From 3286926bbf80fdd0103a372256459e577224f9f6 Mon Sep 17 00:00:00 2001 From: Michi Mutsuzaki Date: Tue, 5 Nov 2024 16:35:28 +0000 Subject: [PATCH] Prepare for v0.16.20 release Update workflow files to accommodate cilium/cilium#35483. Signed-off-by: Michi Mutsuzaki --- .github/workflows/eks-tunnel.yaml | 6 +- .github/workflows/eks.yaml | 6 +- .github/workflows/gke.yaml | 7 +- .github/workflows/kind.yaml | 12 +- .github/workflows/multicluster.yaml | 6 +- README.md | 2 +- RELEASE.md | 2 +- go.mod | 65 +- go.sum | 132 +- vendor/github.com/cilium/charts/README.md | 4 + .../cilium/charts/cilium-1.14.16.tgz | Bin 0 -> 165189 bytes .../cilium/charts/cilium-1.15.10.tgz | Bin 0 -> 180660 bytes .../cilium/charts/cilium-1.16.3.tgz | Bin 0 -> 204585 bytes .../cilium/charts/cilium-1.17.0-pre.1.tgz | Bin 0 -> 210626 bytes .../cilium/charts/generate_helm_release.sh | 2 +- vendor/github.com/cilium/charts/index.yaml | 465 ++++- vendor/github.com/cilium/cilium/AUTHORS | 30 +- .../cilium/cilium/api/v1/flow/flow.pb.go | 864 ++------ .../cilium/api/v1/observer/observer.pb.go | 380 +--- .../api/v1/observer/observer_grpc.pb.go | 2 +- .../cilium/cilium/api/v1/relay/relay.pb.go | 28 +- .../cilium/cilium-cli/cli/clustermesh.go | 1 + .../cilium/cilium/cilium-cli/cli/cmd.go | 4 +- .../cilium/cilium-cli/cli/connectivity.go | 1 + .../cilium/cilium/cilium-cli/cli/hubble.go | 22 +- .../cilium/cilium/cilium-cli/cli/install.go | 4 +- .../cilium-cli/clustermesh/clustermesh.go | 37 +- .../connectivity/builder/builder.go | 7 + .../client_egress_to_cidrgroup_deny.go | 32 + .../connectivity/builder/egress_gateway.go | 3 +- .../builder/egress_gateway_with_l7_policy.go | 3 +- .../builder/from_cidr_host_netns.go | 3 +- ...ent-egress-to-cidrgroup-external-deny.yaml | 27 + .../connectivity/builder/multicast.go | 28 + .../builder/pod_to_pod_encryption.go | 6 +- .../builder/strict_mode_encryption.go | 30 + .../cilium-cli/connectivity/check/action.go | 3 +- .../cilium-cli/connectivity/check/check.go | 1 + .../cilium-cli/connectivity/check/context.go | 39 + .../connectivity/check/deployment.go | 92 + .../cilium-cli/connectivity/check/features.go | 1 + .../cilium-cli/connectivity/check/metrics.go | 2 +- .../cilium-cli/connectivity/check/netshoot.go | 33 + .../cilium-cli/connectivity/check/policy.go | 543 ++--- .../cilium-cli/connectivity/check/result.go | 7 + .../cilium-cli/connectivity/check/test.go | 318 +-- .../cilium-cli/connectivity/check/wait.go | 21 +- .../cilium-cli/connectivity/sniff/sniffer.go | 3 +- .../connectivity/tests/encryption.go | 118 +- .../cilium-cli/connectivity/tests/health.go | 5 +- .../connectivity/tests/multicast.go | 363 ++++ .../cilium-cli/connectivity/tests/pod.go | 96 + .../cilium-cli/connectivity/tests/service.go | 8 - .../cilium/cilium-cli/defaults/defaults.go | 5 +- .../cilium/cilium/cilium-cli/hubble/hubble.go | 7 - .../cilium/cilium/cilium-cli/hubble/relay.go | 28 +- .../cilium/cilium/cilium-cli/hubble/ui.go | 47 +- .../cilium/cilium/cilium-cli/k8s/client.go | 84 +- .../cilium/cilium/cilium-cli/k8s/dialer.go | 100 +- .../cilium/cilium/cilium-cli/k8s/helpers.go | 29 + .../cilium/cilium-cli/multicast/multicast.go | 20 +- .../cilium/cilium/cilium-cli/status/k8s.go | 60 +- .../cilium/cilium/cilium-cli/status/status.go | 10 + .../cilium/cilium-cli/sysdump/client.go | 2 + .../cilium/cilium-cli/sysdump/constants.go | 1 + .../cilium/cilium-cli/sysdump/sysdump.go | 95 +- .../cilium-cli/utils/features/features.go | 15 +- .../cilium/hubble/pkg/printer/printer.go | 88 + .../cilium/cilium/pkg/allocator/allocator.go | 96 +- .../cilium/cilium/pkg/annotation/k8s.go | 7 - .../cilium/cilium/pkg/aws/eni/types/types.go | 5 + .../aws/eni/types/zz_generated.deepequal.go | 4 + .../cilium/pkg/bgpv1/types/fake_router.go | 20 +- .../cilium/cilium/pkg/bgpv1/types/log.go | 3 + .../cilium/pkg/clustermesh/types/option.go | 31 +- .../cilium/pkg/container/bitlpm/cidr.go | 16 + .../cilium/pkg/container/bitlpm/trie.go | 245 ++- .../cilium/cilium/pkg/container/set/set.go | 227 +++ .../cilium/pkg/container/versioned/value.go | 5 +- .../cilium/pkg/controller/controller.go | 7 +- .../linux/probes/managed_neighbors.go | 3 +- .../pkg/datapath/linux/probes/probes.go | 53 + .../linux/safenetlink/netlink_linux.go | 395 ++++ .../linux/safenetlink/netlink_unspecified.go | 141 ++ .../cilium/pkg/datapath/tunnel/tunnel.go | 4 +- .../cilium/pkg/datapath/types/loader.go | 2 +- .../cilium/cilium/pkg/datapath/types/node.go | 4 + .../datapath/types/zz_generated.deepequal.go | 4 + .../cilium/cilium/pkg/defaults/defaults.go | 54 +- .../cilium/cilium/pkg/health/client/client.go | 125 +- .../cilium/pkg/hive/feature_lifecycle.go | 128 -- .../cilium/cilium/pkg/hive/health/metrics.go | 51 +- .../github.com/cilium/cilium/pkg/hive/hive.go | 51 +- .../cilium/pkg/hive/reconciler_metrics.go | 117 +- .../cilium/cilium/pkg/hive/statedb_metrics.go | 73 +- .../cilium/cilium/pkg/hubble/helpers.go | 25 + .../cilium/cilium/pkg/identity/identity.go | 6 + .../pkg/identity/identitymanager/cell.go | 2 +- .../pkg/identity/identitymanager/manager.go | 17 +- .../cilium/cilium/pkg/inctimer/inctimer.go | 80 - vendor/github.com/cilium/cilium/pkg/ip/ip.go | 22 +- .../cilium/cilium/pkg/ipam/types/types.go | 12 + .../pkg/ipam/types/zz_generated.deepcopy.go | 7 + .../pkg/ipam/types/zz_generated.deepequal.go | 24 + .../cilium/cilium/pkg/ipcache/types/types.go | 8 + .../cilium/pkg/k8s/apis/cilium.io/register.go | 2 +- .../pkg/k8s/apis/cilium.io/utils/utils.go | 8 +- .../cilium/pkg/k8s/apis/cilium.io/v2/types.go | 3 - .../cilium.io/v2/zz_generated.deepcopy.go | 5 - .../cilium.io/v2/zz_generated.deepequal.go | 8 - .../cilium.io/v2alpha1/bgp_cluster_types.go | 26 + .../apis/cilium.io/v2alpha1/bgp_peer_types.go | 22 + .../v2alpha1/zz_generated.deepcopy.go | 48 + .../v2alpha1/zz_generated.deepequal.go | 28 + .../cilium/cilium/pkg/k8s/client/cell.go | 127 +- .../v2alpha1/ciliumbgpclusterconfig.go | 2 + .../cilium.io/v2alpha1/ciliumbgppeerconfig.go | 2 + .../fake/fake_ciliumbgpclusterconfig.go | 12 + .../v2alpha1/fake/fake_ciliumbgppeerconfig.go | 12 + .../cilium/cilium/pkg/k8s/client/config.go | 2 +- .../cilium/cilium/pkg/k8s/endpoints.go | 4 +- .../cilium/cilium/pkg/k8s/portforward.go | 198 ++ .../cilium/cilium/pkg/k8s/service.go | 2 +- .../cilium/cilium/pkg/k8s/service_cache.go | 22 +- .../cilium/cilium/pkg/k8s/synced/crd.go | 15 +- .../cilium/cilium/pkg/k8s/synced/resources.go | 3 +- .../cilium/pkg/k8s/testutils/decoder.go | 6 + .../cilium/pkg/k8s/testutils/resources.go | 85 + .../cilium/cilium/pkg/kvstore/dummy.go | 6 +- .../cilium/cilium/pkg/kvstore/etcd.go | 6 +- .../cilium/cilium/pkg/kvstore/lock.go | 5 +- .../cilium/cilium/pkg/labels/arraylist.go | 59 +- .../cilium/pkg/loadbalancer/loadbalancer.go | 12 +- .../cilium/cilium/pkg/logging/slog.go | 30 +- .../cilium/cilium/pkg/mac/mac_linux.go | 6 +- .../cilium/cilium/pkg/monitor/api/files.go | 1 + .../cilium/cilium/pkg/node/address.go | 12 - .../cilium/cilium/pkg/node/address_linux.go | 9 +- .../cilium/cilium/pkg/node/ip_linux.go | 6 +- .../cilium/cilium/pkg/option/config.go | 479 +---- .../cilium/cilium/pkg/policy/api/cidr.go | 105 +- .../cilium/pkg/policy/api/rule_validation.go | 24 +- .../cilium/cilium/pkg/policy/api/selector.go | 4 + .../pkg/policy/api/zz_generated.deepequal.go | 6 + .../cilium/cilium/pkg/policy/cidr.go | 49 +- .../cilium/cilium/pkg/policy/distillery.go | 17 +- .../github.com/cilium/cilium/pkg/policy/l4.go | 225 +-- .../cilium/cilium/pkg/policy/mapstate.go | 1792 +++++++---------- .../cilium/cilium/pkg/policy/repository.go | 170 +- .../cilium/cilium/pkg/policy/resolve.go | 215 +- .../cilium/cilium/pkg/policy/rule.go | 4 +- .../cilium/cilium/pkg/policy/selectorcache.go | 3 +- .../cilium/cilium/pkg/policy/trigger.go | 4 +- .../cilium/cilium/pkg/policy/types/types.go | 6 + .../cilium/cilium/pkg/policy/visibility.go | 235 --- .../cilium/cilium/pkg/resiliency/error.go | 14 + .../cilium/cilium/pkg/resiliency/errorset.go | 63 + .../cilium/cilium/pkg/resiliency/helpers.go | 32 + .../cilium/cilium/pkg/resiliency/retry.go | 13 + .../cilium/cilium/pkg/slices/slices.go | 30 +- .../cilium/cilium/pkg/trigger/trigger.go | 9 +- .../github.com/cilium/hive/cell/lifecycle.go | 22 +- .../cilium/hive/cell/simple_health.go | 59 + vendor/github.com/cilium/hive/hive.go | 17 + vendor/github.com/cilium/hive/script.go | 164 ++ vendor/github.com/cilium/hive/script/LICENSE | 27 + .../github.com/cilium/hive/script/README.md | 4 + .../cilium/hive/script/README.md.original | 11 + vendor/github.com/cilium/hive/script/cmds.go | 1167 +++++++++++ .../cilium/hive/script/cmds_other.go | 11 + .../cilium/hive/script/cmds_posix.go | 16 + vendor/github.com/cilium/hive/script/conds.go | 198 ++ .../github.com/cilium/hive/script/engine.go | 853 ++++++++ .../github.com/cilium/hive/script/errors.go | 64 + .../cilium/hive/script/internal/diff/diff.go | 261 +++ .../cilium/hive/script/makeraw_unix.go | 37 + .../cilium/hive/script/makeraw_unix_bsd.go | 16 + .../cilium/hive/script/makeraw_unix_other.go | 14 + .../cilium/hive/script/makeraw_unsupported.go | 16 + vendor/github.com/cilium/hive/script/state.go | 244 +++ vendor/github.com/cilium/statedb/any_table.go | 145 ++ vendor/github.com/cilium/statedb/cell.go | 1 + vendor/github.com/cilium/statedb/db.go | 43 +- vendor/github.com/cilium/statedb/http.go | 2 +- .../github.com/cilium/statedb/index/bool.go | 7 + vendor/github.com/cilium/statedb/index/int.go | 53 + .../github.com/cilium/statedb/index/netip.go | 16 + .../github.com/cilium/statedb/index/string.go | 4 + .../cilium/statedb/internal/time.go | 41 + vendor/github.com/cilium/statedb/iterator.go | 87 +- vendor/github.com/cilium/statedb/part/map.go | 32 +- vendor/github.com/cilium/statedb/part/set.go | 29 + .../cilium/statedb/reconciler/metrics.go | 12 +- .../cilium/statedb/reconciler/types.go | 65 +- vendor/github.com/cilium/statedb/script.go | 836 ++++++++ vendor/github.com/cilium/statedb/table.go | 67 +- vendor/github.com/cilium/statedb/txn.go | 105 +- vendor/github.com/cilium/statedb/types.go | 96 +- vendor/github.com/fatih/color/README.md | 23 +- vendor/github.com/fatih/color/color.go | 32 +- .../common/model/labelset_string.go | 2 - .../common/model/labelset_string_go120.go | 39 - .../vishvananda/netlink/addr_linux.go | 15 +- .../vishvananda/netlink/bridge_linux.go | 15 +- .../vishvananda/netlink/chain_linux.go | 16 +- .../vishvananda/netlink/class_linux.go | 14 +- .../vishvananda/netlink/conntrack_linux.go | 32 +- .../netlink/conntrack_unspecified.go | 2 +- .../vishvananda/netlink/devlink_linux.go | 40 +- .../vishvananda/netlink/filter_linux.go | 22 +- vendor/github.com/vishvananda/netlink/fou.go | 15 +- .../vishvananda/netlink/fou_linux.go | 66 +- .../vishvananda/netlink/fou_unspecified.go | 1 + .../vishvananda/netlink/genetlink_linux.go | 13 +- .../vishvananda/netlink/gtp_linux.go | 13 +- vendor/github.com/vishvananda/netlink/link.go | 34 +- .../vishvananda/netlink/link_linux.go | 61 +- .../vishvananda/netlink/neigh_linux.go | 34 +- .../vishvananda/netlink/netlink_linux.go | 3 + .../vishvananda/netlink/nl/link_linux.go | 2 + .../vishvananda/netlink/nl/nl_linux.go | 108 +- .../vishvananda/netlink/protinfo_linux.go | 13 +- .../vishvananda/netlink/qdisc_linux.go | 15 +- .../vishvananda/netlink/rdma_link_linux.go | 24 +- .../vishvananda/netlink/route_linux.go | 30 +- .../vishvananda/netlink/rule_linux.go | 21 +- .../vishvananda/netlink/socket_linux.go | 109 +- .../vishvananda/netlink/socket_xdp_linux.go | 18 +- .../vishvananda/netlink/vdpa_linux.go | 60 +- .../vishvananda/netlink/xfrm_policy_linux.go | 15 +- .../vishvananda/netlink/xfrm_state_linux.go | 15 +- vendor/go.opentelemetry.io/otel/.golangci.yml | 7 + vendor/go.opentelemetry.io/otel/CHANGELOG.md | 32 +- vendor/go.opentelemetry.io/otel/CODEOWNERS | 4 +- .../go.opentelemetry.io/otel/CONTRIBUTING.md | 11 +- vendor/go.opentelemetry.io/otel/Makefile | 5 +- vendor/go.opentelemetry.io/otel/README.md | 4 +- vendor/go.opentelemetry.io/otel/RELEASING.md | 11 - .../go.opentelemetry.io/otel/attribute/set.go | 40 +- .../otel/internal/global/meter.go | 99 +- .../otel/internal/rawhelpers.go | 3 +- .../otel/metric/instrument.go | 2 +- vendor/go.opentelemetry.io/otel/renovate.json | 4 + .../otel/verify_examples.sh | 74 - vendor/go.opentelemetry.io/otel/version.go | 2 +- vendor/go.opentelemetry.io/otel/versions.yaml | 8 +- vendor/golang.org/x/net/http2/config.go | 122 ++ vendor/golang.org/x/net/http2/config_go124.go | 61 + .../x/net/http2/config_pre_go124.go | 16 + vendor/golang.org/x/net/http2/http2.go | 53 +- vendor/golang.org/x/net/http2/server.go | 181 +- vendor/golang.org/x/net/http2/transport.go | 143 +- vendor/golang.org/x/net/http2/write.go | 10 + .../golang.org/x/net/websocket/websocket.go | 2 +- vendor/golang.org/x/oauth2/token.go | 7 + vendor/golang.org/x/sys/unix/README.md | 2 +- vendor/golang.org/x/sys/unix/mkerrors.sh | 4 +- vendor/golang.org/x/sys/unix/syscall_aix.go | 2 +- vendor/golang.org/x/sys/unix/syscall_linux.go | 63 +- .../x/sys/unix/syscall_linux_arm64.go | 2 + .../x/sys/unix/syscall_linux_loong64.go | 2 + .../x/sys/unix/syscall_linux_riscv64.go | 2 + .../golang.org/x/sys/unix/vgetrandom_linux.go | 13 + .../x/sys/unix/vgetrandom_unsupported.go | 11 + vendor/golang.org/x/sys/unix/zerrors_linux.go | 13 +- .../x/sys/unix/zerrors_linux_386.go | 5 + .../x/sys/unix/zerrors_linux_amd64.go | 5 + .../x/sys/unix/zerrors_linux_arm.go | 5 + .../x/sys/unix/zerrors_linux_arm64.go | 5 + .../x/sys/unix/zerrors_linux_loong64.go | 5 + .../x/sys/unix/zerrors_linux_mips.go | 5 + .../x/sys/unix/zerrors_linux_mips64.go | 5 + .../x/sys/unix/zerrors_linux_mips64le.go | 5 + .../x/sys/unix/zerrors_linux_mipsle.go | 5 + .../x/sys/unix/zerrors_linux_ppc.go | 5 + .../x/sys/unix/zerrors_linux_ppc64.go | 5 + .../x/sys/unix/zerrors_linux_ppc64le.go | 5 + .../x/sys/unix/zerrors_linux_riscv64.go | 5 + .../x/sys/unix/zerrors_linux_s390x.go | 5 + .../x/sys/unix/zerrors_linux_sparc64.go | 5 + .../golang.org/x/sys/unix/zsyscall_linux.go | 17 - .../x/sys/unix/zsysnum_linux_amd64.go | 1 + .../x/sys/unix/zsysnum_linux_arm64.go | 2 +- .../x/sys/unix/zsysnum_linux_loong64.go | 2 + .../x/sys/unix/zsysnum_linux_riscv64.go | 2 +- vendor/golang.org/x/sys/unix/ztypes_linux.go | 88 +- .../golang.org/x/sys/windows/dll_windows.go | 2 +- vendor/golang.org/x/time/rate/rate.go | 17 +- vendor/golang.org/x/tools/LICENSE | 27 + vendor/golang.org/x/tools/PATENTS | 22 + vendor/golang.org/x/tools/txtar/archive.go | 140 ++ vendor/golang.org/x/tools/txtar/fs.go | 257 +++ .../grpc/internal/transport/transport.go | 2 +- vendor/google.golang.org/grpc/version.go | 2 +- .../protobuf/encoding/protojson/decode.go | 2 +- .../protobuf/encoding/protojson/encode.go | 4 +- .../protobuf/internal/descopts/options.go | 20 +- .../internal/editionssupport/editions.go | 2 +- .../protobuf/internal/filedesc/desc.go | 4 + .../protobuf/internal/filedesc/desc_init.go | 2 + .../protobuf/internal/filedesc/desc_lazy.go | 2 + .../protobuf/internal/filedesc/editions.go | 2 +- .../protobuf/internal/genid/doc.go | 2 +- .../internal/genid/go_features_gen.go | 15 +- .../protobuf/internal/genid/map_entry.go | 2 +- .../protobuf/internal/genid/wrappers.go | 2 +- .../protobuf/internal/impl/codec_extension.go | 11 +- .../protobuf/internal/impl/codec_field.go | 3 + .../protobuf/internal/impl/codec_message.go | 3 + .../protobuf/internal/impl/codec_reflect.go | 210 -- .../protobuf/internal/impl/codec_unsafe.go | 3 - .../protobuf/internal/impl/convert.go | 2 +- .../protobuf/internal/impl/encode.go | 2 +- .../protobuf/internal/impl/equal.go | 224 +++ .../internal/impl/legacy_extension.go | 1 + .../protobuf/internal/impl/message.go | 4 +- .../protobuf/internal/impl/pointer_reflect.go | 215 -- .../protobuf/internal/impl/pointer_unsafe.go | 3 - .../protobuf/internal/strs/strings_pure.go | 28 - .../internal/strs/strings_unsafe_go120.go | 3 +- .../internal/strs/strings_unsafe_go121.go | 3 +- .../protobuf/internal/version/version.go | 4 +- .../google.golang.org/protobuf/proto/equal.go | 9 + .../protobuf/proto/extension.go | 71 + .../protobuf/reflect/protodesc/desc_init.go | 4 + .../protobuf/reflect/protodesc/editions.go | 2 +- .../protobuf/reflect/protoreflect/methods.go | 10 + .../reflect/protoreflect/value_pure.go | 60 - .../protoreflect/value_unsafe_go120.go | 3 +- .../protoreflect/value_unsafe_go121.go | 3 +- .../protobuf/runtime/protoiface/methods.go | 18 + .../types/descriptorpb/descriptor.pb.go | 748 ++----- .../types/gofeaturespb/go_features.pb.go | 24 +- .../protobuf/types/known/anypb/any.pb.go | 24 +- .../types/known/durationpb/duration.pb.go | 24 +- .../protobuf/types/known/emptypb/empty.pb.go | 24 +- .../types/known/fieldmaskpb/field_mask.pb.go | 24 +- .../types/known/structpb/struct.pb.go | 110 +- .../types/known/timestamppb/timestamp.pb.go | 24 +- .../types/known/wrapperspb/wrappers.pb.go | 200 +- .../helm/v3/internal/resolver/resolver.go | 2 +- .../helm/v3/internal/third_party/dep/fs/fs.go | 2 +- .../helm.sh/helm/v3/internal/tlsutil/tls.go | 2 +- vendor/helm.sh/helm/v3/pkg/action/install.go | 8 +- vendor/helm.sh/helm/v3/pkg/action/upgrade.go | 2 +- .../helm/v3/pkg/chart/loader/archive.go | 2 +- .../helm/v3/pkg/chartutil/dependencies.go | 2 +- .../helm.sh/helm/v3/pkg/cli/output/output.go | 2 +- .../helm.sh/helm/v3/pkg/downloader/manager.go | 2 +- vendor/helm.sh/helm/v3/pkg/engine/engine.go | 2 +- .../helm.sh/helm/v3/pkg/engine/lookup_func.go | 2 +- .../helm.sh/helm/v3/pkg/helmpath/lazypath.go | 2 +- vendor/helm.sh/helm/v3/pkg/ignore/doc.go | 2 +- vendor/helm.sh/helm/v3/pkg/kube/client.go | 4 +- vendor/helm.sh/helm/v3/pkg/kube/ready.go | 2 +- vendor/helm.sh/helm/v3/pkg/registry/util.go | 3 +- vendor/helm.sh/helm/v3/pkg/release/status.go | 4 +- vendor/helm.sh/helm/v3/pkg/repo/index.go | 6 +- .../helm.sh/helm/v3/pkg/storage/driver/sql.go | 12 +- vendor/helm.sh/helm/v3/pkg/strvals/parser.go | 2 +- vendor/helm.sh/helm/v3/pkg/time/time.go | 2 +- vendor/k8s.io/kubectl/pkg/util/apply.go | 146 ++ vendor/k8s.io/kubectl/pkg/util/pod_port.go | 42 + .../kubectl/pkg/util/podutils/podutils.go | 251 +++ .../k8s.io/kubectl/pkg/util/service_port.go | 59 + vendor/k8s.io/kubectl/pkg/util/umask.go | 29 + .../k8s.io/kubectl/pkg/util/umask_windows.go | 29 + vendor/k8s.io/kubectl/pkg/util/util.go | 93 + vendor/modules.txt | 80 +- .../apis/v1alpha2/grpcroute_types.go | 1 + .../apis/v1alpha2/referencegrant_types.go | 1 + 371 files changed, 14213 insertions(+), 7012 deletions(-) create mode 100644 vendor/github.com/cilium/charts/cilium-1.14.16.tgz create mode 100644 vendor/github.com/cilium/charts/cilium-1.15.10.tgz create mode 100644 vendor/github.com/cilium/charts/cilium-1.16.3.tgz create mode 100644 vendor/github.com/cilium/charts/cilium-1.17.0-pre.1.tgz create mode 100644 vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/client_egress_to_cidrgroup_deny.go create mode 100644 vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/manifests/client-egress-to-cidrgroup-external-deny.yaml create mode 100644 vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/multicast.go create mode 100644 vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/strict_mode_encryption.go create mode 100644 vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/netshoot.go create mode 100644 vendor/github.com/cilium/cilium/cilium-cli/connectivity/tests/multicast.go create mode 100644 vendor/github.com/cilium/cilium/pkg/container/set/set.go create mode 100644 vendor/github.com/cilium/cilium/pkg/datapath/linux/safenetlink/netlink_linux.go create mode 100644 vendor/github.com/cilium/cilium/pkg/datapath/linux/safenetlink/netlink_unspecified.go delete mode 100644 vendor/github.com/cilium/cilium/pkg/hive/feature_lifecycle.go create mode 100644 vendor/github.com/cilium/cilium/pkg/hubble/helpers.go delete mode 100644 vendor/github.com/cilium/cilium/pkg/inctimer/inctimer.go create mode 100644 vendor/github.com/cilium/cilium/pkg/k8s/portforward.go create mode 100644 vendor/github.com/cilium/cilium/pkg/k8s/testutils/resources.go delete mode 100644 vendor/github.com/cilium/cilium/pkg/policy/visibility.go create mode 100644 vendor/github.com/cilium/cilium/pkg/resiliency/error.go create mode 100644 vendor/github.com/cilium/cilium/pkg/resiliency/errorset.go create mode 100644 vendor/github.com/cilium/cilium/pkg/resiliency/helpers.go create mode 100644 vendor/github.com/cilium/cilium/pkg/resiliency/retry.go create mode 100644 vendor/github.com/cilium/hive/script.go create mode 100644 vendor/github.com/cilium/hive/script/LICENSE create mode 100644 vendor/github.com/cilium/hive/script/README.md create mode 100644 vendor/github.com/cilium/hive/script/README.md.original create mode 100644 vendor/github.com/cilium/hive/script/cmds.go create mode 100644 vendor/github.com/cilium/hive/script/cmds_other.go create mode 100644 vendor/github.com/cilium/hive/script/cmds_posix.go create mode 100644 vendor/github.com/cilium/hive/script/conds.go create mode 100644 vendor/github.com/cilium/hive/script/engine.go create mode 100644 vendor/github.com/cilium/hive/script/errors.go create mode 100644 vendor/github.com/cilium/hive/script/internal/diff/diff.go create mode 100644 vendor/github.com/cilium/hive/script/makeraw_unix.go create mode 100644 vendor/github.com/cilium/hive/script/makeraw_unix_bsd.go create mode 100644 vendor/github.com/cilium/hive/script/makeraw_unix_other.go create mode 100644 vendor/github.com/cilium/hive/script/makeraw_unsupported.go create mode 100644 vendor/github.com/cilium/hive/script/state.go create mode 100644 vendor/github.com/cilium/statedb/any_table.go create mode 100644 vendor/github.com/cilium/statedb/internal/time.go create mode 100644 vendor/github.com/cilium/statedb/script.go delete mode 100644 vendor/github.com/prometheus/common/model/labelset_string_go120.go delete mode 100644 vendor/go.opentelemetry.io/otel/verify_examples.sh create mode 100644 vendor/golang.org/x/net/http2/config.go create mode 100644 vendor/golang.org/x/net/http2/config_go124.go create mode 100644 vendor/golang.org/x/net/http2/config_pre_go124.go create mode 100644 vendor/golang.org/x/sys/unix/vgetrandom_linux.go create mode 100644 vendor/golang.org/x/sys/unix/vgetrandom_unsupported.go create mode 100644 vendor/golang.org/x/tools/LICENSE create mode 100644 vendor/golang.org/x/tools/PATENTS create mode 100644 vendor/golang.org/x/tools/txtar/archive.go create mode 100644 vendor/golang.org/x/tools/txtar/fs.go delete mode 100644 vendor/google.golang.org/protobuf/internal/impl/codec_reflect.go create mode 100644 vendor/google.golang.org/protobuf/internal/impl/equal.go delete mode 100644 vendor/google.golang.org/protobuf/internal/impl/pointer_reflect.go delete mode 100644 vendor/google.golang.org/protobuf/internal/strs/strings_pure.go delete mode 100644 vendor/google.golang.org/protobuf/reflect/protoreflect/value_pure.go create mode 100644 vendor/k8s.io/kubectl/pkg/util/apply.go create mode 100644 vendor/k8s.io/kubectl/pkg/util/pod_port.go create mode 100644 vendor/k8s.io/kubectl/pkg/util/podutils/podutils.go create mode 100644 vendor/k8s.io/kubectl/pkg/util/service_port.go create mode 100644 vendor/k8s.io/kubectl/pkg/util/umask.go create mode 100644 vendor/k8s.io/kubectl/pkg/util/umask_windows.go create mode 100644 vendor/k8s.io/kubectl/pkg/util/util.go diff --git a/.github/workflows/eks-tunnel.yaml b/.github/workflows/eks-tunnel.yaml index a505625983..8bd59956d4 100644 --- a/.github/workflows/eks-tunnel.yaml +++ b/.github/workflows/eks-tunnel.yaml @@ -158,11 +158,7 @@ jobs: # Port forward Relay cilium hubble port-forward& sleep 10s - if ! [[ $(pgrep -f "kubectl.*port-forward.*hubble-relay" | wc -l) == 1 ]]; then - # support for native port-forwarding - # TODO: remove kubectl version after 0.16.20 release - [[ $(pgrep -f "cilium.*hubble.*port-forward" | wc -l) == 1 ]] - fi + [[ $(pgrep -f "^cilium.*hubble.*port-forward$" | wc -l) == 1 ]] # Run connectivity test cilium connectivity test --test-concurrency=3 --all-flows --collect-sysdump-on-failure --external-target amazon.com. \ diff --git a/.github/workflows/eks.yaml b/.github/workflows/eks.yaml index eaffeb7670..e742563055 100644 --- a/.github/workflows/eks.yaml +++ b/.github/workflows/eks.yaml @@ -157,11 +157,7 @@ jobs: # Port forward Relay cilium hubble port-forward& sleep 10s - if ! [[ $(pgrep -f "kubectl.*port-forward.*hubble-relay" | wc -l) == 1 ]]; then - # support for native port-forwarding - # TODO: remove kubectl version after 0.16.20 release - [[ $(pgrep -f "cilium.*hubble.*port-forward" | wc -l) == 1 ]] - fi + [[ $(pgrep -f "^cilium.*hubble.*port-forward$" | wc -l) == 1 ]] # Run connectivity test cilium connectivity test --test-concurrency=3 --all-flows --collect-sysdump-on-failure --external-target amazon.com. diff --git a/.github/workflows/gke.yaml b/.github/workflows/gke.yaml index 437f9a9837..8ed1b4a54d 100644 --- a/.github/workflows/gke.yaml +++ b/.github/workflows/gke.yaml @@ -153,11 +153,8 @@ jobs: # Port forward Relay cilium hubble port-forward& sleep 10s - if ! [[ $(pgrep -f "kubectl.*port-forward.*hubble-relay" | wc -l) == 1 ]]; then - # support for native port-forwarding - # TODO: remove kubectl version after 0.16.20 release - [[ $(pgrep -f "cilium.*hubble.*port-forward" | wc -l) == 1 ]] - fi + + [[ $(pgrep -f "^cilium.*hubble.*port-forward$" | wc -l) == 1 ]] # Run connectivity test cilium connectivity test --test-concurrency=5 --all-flows --collect-sysdump-on-failure --external-target google.com. diff --git a/.github/workflows/kind.yaml b/.github/workflows/kind.yaml index be0f54b57d..9d3a5828c1 100644 --- a/.github/workflows/kind.yaml +++ b/.github/workflows/kind.yaml @@ -82,11 +82,7 @@ jobs: run: | cilium hubble port-forward& sleep 10s - if ! [[ $(pgrep -f "kubectl.*port-forward.*hubble-relay" | wc -l) == 1 ]]; then - # support for native port-forwarding - # TODO: remove kubectl version after 0.16.20 release - [[ $(pgrep -f "cilium.*hubble.*port-forward" | wc -l) == 1 ]] - fi + [[ $(pgrep -f "^cilium.*hubble.*port-forward$" | wc -l) == 1 ]] - name: Set up external targets id: external_targets @@ -164,11 +160,7 @@ jobs: run: | cilium hubble port-forward& sleep 10s - if ! [[ $(pgrep -f "kubectl.*port-forward.*hubble-relay" | wc -l) == 1 ]]; then - # support for native port-forwarding - # TODO: remove kubectl version after 0.16.20 release - [[ $(pgrep -f "cilium.*hubble.*port-forward" | wc -l) == 1 ]] - fi + [[ $(pgrep -f "^cilium.*hubble.*port-forward$" | wc -l) == 1 ]] - name: Connectivity test run: | diff --git a/.github/workflows/multicluster.yaml b/.github/workflows/multicluster.yaml index 9ba005c7a4..561580c642 100644 --- a/.github/workflows/multicluster.yaml +++ b/.github/workflows/multicluster.yaml @@ -250,11 +250,7 @@ jobs: # Port forward Relay cilium --context "${{ steps.contexts.outputs.cluster1 }}" hubble port-forward& sleep 10s - if ! [[ $(pgrep -f "kubectl.*port-forward.*hubble-relay" | wc -l) == 1 ]]; then - # support for native port-forwarding - # TODO: remove kubectl version after 0.16.20 release - [[ $(pgrep -f "cilium.*hubble.*port-forward" | wc -l) == 1 ]] - fi + [[ $(pgrep -f "^cilium.*hubble.*port-forward$" | wc -l) == 1 ]] # Run connectivity test cilium --context "${{ steps.contexts.outputs.cluster1 }}" connectivity test --test-concurrency=5 \ diff --git a/README.md b/README.md index 620fe696f1..3e0e6f44a4 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ binary releases. | Release | Maintained | Compatible Cilium Versions | |------------------------------------------------------------------------|------------|----------------------------| -| [v0.16.19](https://github.com/cilium/cilium-cli/releases/tag/v0.16.19) | Yes | Cilium 1.15 and newer | +| [v0.16.20](https://github.com/cilium/cilium-cli/releases/tag/v0.16.20) | Yes | Cilium 1.15 and newer | | [v0.15.22](https://github.com/cilium/cilium-cli/releases/tag/v0.15.22) | Yes | Cilium 1.14 (*) | Note: diff --git a/RELEASE.md b/RELEASE.md index 925d50a4c0..7c395e480e 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -19,7 +19,7 @@ table](https://github.com/cilium/cilium-cli#releases) for the most recent suppor Set `RELEASE` environment variable to the new version. This variable will be used in the commands throughout the documenat to allow copy-pasting. - export RELEASE=v0.16.20 + export RELEASE=v0.16.21 ## Update local checkout diff --git a/go.mod b/go.mod index 284225595c..93d82cb1bf 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ replace ( sigs.k8s.io/controller-tools => github.com/cilium/controller-tools v0.8.0-2 ) -require github.com/cilium/cilium v1.17.0-pre.1 +require github.com/cilium/cilium v1.17.0-pre.2 require ( cel.dev/expr v0.16.0 // indirect @@ -32,11 +32,11 @@ require ( github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect - github.com/cilium/charts v0.0.0-20240926142256-e20f2b5f5344 // indirect + github.com/cilium/charts v0.0.0-20241015090923-1f4c1b5ac12a // indirect github.com/cilium/ebpf v0.16.0 // indirect - github.com/cilium/hive v0.0.0-20240926131619-aa37668760f2 // indirect + github.com/cilium/hive v0.0.0-20241021113747-bb8f3c0bede4 // indirect github.com/cilium/proxy v0.0.0-20240909042906-ae435a5bef38 // indirect - github.com/cilium/statedb v0.3.0 // indirect + github.com/cilium/statedb v0.3.2 // indirect github.com/cilium/stream v0.0.0-20240816054136-71321e385273 // indirect github.com/cilium/workerpool v1.2.0 // indirect github.com/cloudflare/cfssl v1.6.5 // indirect @@ -58,7 +58,7 @@ require ( github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect github.com/evanphx/json-patch v5.9.0+incompatible // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect - github.com/fatih/color v1.17.0 // indirect + github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect @@ -137,9 +137,9 @@ require ( github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/prometheus/client_golang v1.20.4 // indirect + github.com/prometheus/client_golang v1.20.5 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.59.1 // indirect + github.com/prometheus/common v0.60.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/rubenv/sql-migrate v1.7.0 // indirect @@ -156,7 +156,7 @@ require ( github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace // indirect github.com/spf13/viper v1.19.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect - github.com/vishvananda/netlink v1.3.0 // indirect + github.com/vishvananda/netlink v1.3.1-0.20241022031324-976bd8de7d81 // indirect github.com/vishvananda/netns v0.0.4 // indirect github.com/weppos/publicsuffix-go v0.30.0 // indirect github.com/x448/float16 v0.8.4 // indirect @@ -171,52 +171,53 @@ require ( go.etcd.io/etcd/client/v3 v3.5.16 // indirect go.mongodb.org/mongo-driver v1.14.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect - go.opentelemetry.io/otel v1.30.0 // indirect - go.opentelemetry.io/otel/metric v1.30.0 // indirect - go.opentelemetry.io/otel/trace v1.30.0 // indirect + go.opentelemetry.io/otel v1.31.0 // indirect + go.opentelemetry.io/otel/metric v1.31.0 // indirect + go.opentelemetry.io/otel/trace v1.31.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect go.uber.org/dig v1.17.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect - golang.org/x/net v0.29.0 // indirect - golang.org/x/oauth2 v0.22.0 // indirect + golang.org/x/net v0.30.0 // indirect + golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect - golang.org/x/time v0.6.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect + golang.org/x/time v0.7.0 // indirect + golang.org/x/tools v0.26.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240924160255-9d4c2d233b61 // indirect - google.golang.org/grpc v1.67.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect + google.golang.org/grpc v1.67.1 // indirect + google.golang.org/protobuf v1.35.1 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - helm.sh/helm/v3 v3.16.1 // indirect - k8s.io/api v0.31.1 // indirect - k8s.io/apiextensions-apiserver v0.31.1 // indirect - k8s.io/apimachinery v0.31.1 // indirect - k8s.io/apiserver v0.31.1 // indirect - k8s.io/cli-runtime v0.31.1 // indirect - k8s.io/client-go v0.31.1 // indirect - k8s.io/component-base v0.31.1 // indirect + helm.sh/helm/v3 v3.16.2 // indirect + k8s.io/api v0.31.2 // indirect + k8s.io/apiextensions-apiserver v0.31.2 // indirect + k8s.io/apimachinery v0.31.2 // indirect + k8s.io/apiserver v0.31.2 // indirect + k8s.io/cli-runtime v0.31.2 // indirect + k8s.io/client-go v0.31.2 // indirect + k8s.io/component-base v0.31.2 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20240423202451-8948a665c108 // indirect - k8s.io/kubectl v0.31.0 // indirect + k8s.io/kubectl v0.31.1 // indirect k8s.io/utils v0.0.0-20240921022957-49e7df575cb6 // indirect oras.land/oras-go v1.2.5 // indirect sigs.k8s.io/controller-runtime v0.19.0 // indirect - sigs.k8s.io/gateway-api v1.2.0-rc1.0.20240923191000-5c5fc388829d // indirect + sigs.k8s.io/gateway-api v1.2.0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/kustomize/api v0.17.2 // indirect sigs.k8s.io/kustomize/kyaml v0.17.1 // indirect - sigs.k8s.io/mcs-api v0.1.1-0.20240919125245-7bbb5990134a // indirect + sigs.k8s.io/mcs-api v0.1.1-0.20241002142749-eff1ba8c3ab2 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index 801ce2097f..5086cac10e 100644 --- a/go.sum +++ b/go.sum @@ -60,18 +60,18 @@ github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHe github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cilium/charts v0.0.0-20240926142256-e20f2b5f5344 h1:biAN1Y6c+77q59tDU74YzXB8ffZoe2KyryMU12PWB3k= -github.com/cilium/charts v0.0.0-20240926142256-e20f2b5f5344/go.mod h1:M3C9VOlFvRzuV+a01t07Tw4uFLSfkCH3L542IWjf6BU= -github.com/cilium/cilium v1.17.0-pre.1 h1:HIJgJ8mtGrz6fgRI6YA/TPAsx2s06rmJTvmVe8RiilA= -github.com/cilium/cilium v1.17.0-pre.1/go.mod h1:OM+QqlLdnaaQiGA9/OTTeVDBLyZYtwVDIbcTMMAm1gU= +github.com/cilium/charts v0.0.0-20241015090923-1f4c1b5ac12a h1:jyDHmM2GFbdsljXhgMbJ1Hc9bdNunKtzhjgiMEBKlNA= +github.com/cilium/charts v0.0.0-20241015090923-1f4c1b5ac12a/go.mod h1:M3C9VOlFvRzuV+a01t07Tw4uFLSfkCH3L542IWjf6BU= +github.com/cilium/cilium v1.17.0-pre.2 h1:Y9J4EalQEmtInHirFRF9qK7yAid3WS8/2A3/hnEfsV4= +github.com/cilium/cilium v1.17.0-pre.2/go.mod h1:OoSUlF3lvdYOmVccOYWZnfs9tDHDyEWTtrwjlq8pDYA= github.com/cilium/ebpf v0.16.0 h1:+BiEnHL6Z7lXnlGUsXQPPAE7+kenAd4ES8MQ5min0Ok= github.com/cilium/ebpf v0.16.0/go.mod h1:L7u2Blt2jMM/vLAVgjxluxtBKlz3/GWjB0dMOEngfwE= -github.com/cilium/hive v0.0.0-20240926131619-aa37668760f2 h1:xZn7yvMbK1+Au6D/YLKEMdcEsyMt6Qu7CUv+yQHGqv0= -github.com/cilium/hive v0.0.0-20240926131619-aa37668760f2/go.mod h1:6tW1eCwSq8Wz8IVtpZE0MemoCWSrEOUa8aLKotmBRCo= +github.com/cilium/hive v0.0.0-20241021113747-bb8f3c0bede4 h1:dTnQNUDijFP+hf7soSZtoBYZ1OTV7qATqE+qbb//zUQ= +github.com/cilium/hive v0.0.0-20241021113747-bb8f3c0bede4/go.mod h1:pI2GJ1n3SLKIQVFrKF7W6A6gb6BQkZ+3Hp4PAEo5SuI= github.com/cilium/proxy v0.0.0-20240909042906-ae435a5bef38 h1:hbRPkcebWy1ZqqcnwiJJCFyzLa+xnXk6G/sM/eAsnrU= github.com/cilium/proxy v0.0.0-20240909042906-ae435a5bef38/go.mod h1:5L6S+WQ9v24ibJq38EMHEDljPrdx6PHqTDSHxRCJL2g= -github.com/cilium/statedb v0.3.0 h1:RpM6r1+gv8TY6V18DcrcMbGaoCBs0Vf9z7OxGCbPVaQ= -github.com/cilium/statedb v0.3.0/go.mod h1:AvMKi/i8VISTCvymtkTKSkz1uLlzuiYeaF8jvJO8ymU= +github.com/cilium/statedb v0.3.2 h1:gXjEEVv/zSNU41nHjlhOVqqpTWnMt+l+9Z+FhBnqCSk= +github.com/cilium/statedb v0.3.2/go.mod h1:KEdRTPdh54Asl6qimsUh6zFHVprL6ijp9/gCC+/3uA0= github.com/cilium/stream v0.0.0-20240816054136-71321e385273 h1:lyP0p5AW9fnNWmUcQ/BKaOmBEyZ+VWY1mGT1CFWv2b0= github.com/cilium/stream v0.0.0-20240816054136-71321e385273/go.mod h1:/e83AwqvNKpyg4n3C41qmnmj1x2G9DwzI+jb7GkF4lI= github.com/cilium/workerpool v1.2.0 h1:Wc2iOPTvCgWKQXeq4L5tnx4QFEI+z5q1+bSpSS0cnAY= @@ -135,8 +135,8 @@ github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= -github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= @@ -407,8 +407,8 @@ github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjz github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= -github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -416,8 +416,8 @@ github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJoX0= -github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0= +github.com/prometheus/common v0.60.0 h1:+V9PAREWNvJMAuJ1x1BaWl9dewMW4YrHZQbx0sJNllA= +github.com/prometheus/common v0.60.0/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= @@ -479,8 +479,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk= -github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs= +github.com/vishvananda/netlink v1.3.1-0.20241022031324-976bd8de7d81 h1:9fkQcQYvtTr9ayFXuMfDMVuDt4+BYG9FwsGLnrBde0M= +github.com/vishvananda/netlink v1.3.1-0.20241022031324-976bd8de7d81/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/weppos/publicsuffix-go v0.12.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k= @@ -531,14 +531,14 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= -go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= -go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= -go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= -go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= +go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= +go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= +go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= +go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= -go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= -go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= +go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= +go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY= @@ -561,8 +561,8 @@ golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= @@ -593,11 +593,11 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= -golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -634,26 +634,26 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= -golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= +golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -664,8 +664,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= -golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= +golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -677,13 +677,13 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240924160255-9d4c2d233b61 h1:N9BgCIAUvn/M+p4NJccWPWb3BWh88+zyL0ll9HgbEeM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240924160255-9d4c2d233b61/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= -google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -692,8 +692,8 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -716,46 +716,46 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -helm.sh/helm/v3 v3.16.1 h1:cER6tI/8PgUAsaJaQCVBUg3VI9KN4oVaZJgY60RIc0c= -helm.sh/helm/v3 v3.16.1/go.mod h1:r+xBHHP20qJeEqtvBXMf7W35QDJnzY/eiEBzt+TfHps= +helm.sh/helm/v3 v3.16.2 h1:Y9v7ry+ubQmi+cb5zw1Llx8OKHU9Hk9NQ/+P+LGBe2o= +helm.sh/helm/v3 v3.16.2/go.mod h1:SyTXgKBjNqi2NPsHCW5dDAsHqvGIu0kdNYNH9gQaw70= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU= -k8s.io/api v0.31.1/go.mod h1:sbN1g6eY6XVLeqNsZGLnI5FwVseTrZX7Fv3O26rhAaI= -k8s.io/apiextensions-apiserver v0.31.1 h1:L+hwULvXx+nvTYX/MKM3kKMZyei+UiSXQWciX/N6E40= -k8s.io/apiextensions-apiserver v0.31.1/go.mod h1:tWMPR3sgW+jsl2xm9v7lAyRF1rYEK71i9G5dRtkknoQ= -k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U= -k8s.io/apimachinery v0.31.1/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= -k8s.io/apiserver v0.31.1 h1:Sars5ejQDCRBY5f7R3QFHdqN3s61nhkpaX8/k1iEw1c= -k8s.io/apiserver v0.31.1/go.mod h1:lzDhpeToamVZJmmFlaLwdYZwd7zB+WYRYIboqA1kGxM= -k8s.io/cli-runtime v0.31.1 h1:/ZmKhmZ6hNqDM+yf9s3Y4KEYakNXUn5sod2LWGGwCuk= -k8s.io/cli-runtime v0.31.1/go.mod h1:pKv1cDIaq7ehWGuXQ+A//1OIF+7DI+xudXtExMCbe9U= -k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0= -k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg= -k8s.io/component-base v0.31.1 h1:UpOepcrX3rQ3ab5NB6g5iP0tvsgJWzxTyAo20sgYSy8= -k8s.io/component-base v0.31.1/go.mod h1:WGeaw7t/kTsqpVTaCoVEtillbqAhF2/JgvO0LDOMa0w= +k8s.io/api v0.31.2 h1:3wLBbL5Uom/8Zy98GRPXpJ254nEFpl+hwndmk9RwmL0= +k8s.io/api v0.31.2/go.mod h1:bWmGvrGPssSK1ljmLzd3pwCQ9MgoTsRCuK35u6SygUk= +k8s.io/apiextensions-apiserver v0.31.2 h1:W8EwUb8+WXBLu56ser5IudT2cOho0gAKeTOnywBLxd0= +k8s.io/apiextensions-apiserver v0.31.2/go.mod h1:i+Geh+nGCJEGiCGR3MlBDkS7koHIIKWVfWeRFiOsUcM= +k8s.io/apimachinery v0.31.2 h1:i4vUt2hPK56W6mlT7Ry+AO8eEsyxMD1U44NR22CLTYw= +k8s.io/apimachinery v0.31.2/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/apiserver v0.31.2 h1:VUzOEUGRCDi6kX1OyQ801m4A7AUPglpsmGvdsekmcI4= +k8s.io/apiserver v0.31.2/go.mod h1:o3nKZR7lPlJqkU5I3Ove+Zx3JuoFjQobGX1Gctw6XuE= +k8s.io/cli-runtime v0.31.2 h1:7FQt4C4Xnqx8V1GJqymInK0FFsoC+fAZtbLqgXYVOLQ= +k8s.io/cli-runtime v0.31.2/go.mod h1:XROyicf+G7rQ6FQJMbeDV9jqxzkWXTYD6Uxd15noe0Q= +k8s.io/client-go v0.31.2 h1:Y2F4dxU5d3AQj+ybwSMqQnpZH9F30//1ObxOKlTI9yc= +k8s.io/client-go v0.31.2/go.mod h1:NPa74jSVR/+eez2dFsEIHNa+3o09vtNaWwWwb1qSxSs= +k8s.io/component-base v0.31.2 h1:Z1J1LIaC0AV+nzcPRFqfK09af6bZ4D1nAOpWsy9owlA= +k8s.io/component-base v0.31.2/go.mod h1:9PeyyFN/drHjtJZMCTkSpQJS3U9OXORnHQqMLDz0sUQ= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20240423202451-8948a665c108 h1:Q8Z7VlGhcJgBHJHYugJ/K/7iB8a2eSxCyxdVjJp+lLY= k8s.io/kube-openapi v0.0.0-20240423202451-8948a665c108/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= -k8s.io/kubectl v0.31.0 h1:kANwAAPVY02r4U4jARP/C+Q1sssCcN/1p9Nk+7BQKVg= -k8s.io/kubectl v0.31.0/go.mod h1:pB47hhFypGsaHAPjlwrNbvhXgmuAr01ZBvAIIUaI8d4= +k8s.io/kubectl v0.31.1 h1:ih4JQJHxsEggFqDJEHSOdJ69ZxZftgeZvYo7M/cpp24= +k8s.io/kubectl v0.31.1/go.mod h1:aNuQoR43W6MLAtXQ/Bu4GDmoHlbhHKuyD49lmTC8eJM= k8s.io/utils v0.0.0-20240921022957-49e7df575cb6 h1:MDF6h2H/h4tbzmtIKTuctcwZmY0tY9mD9fNT47QO6HI= k8s.io/utils v0.0.0-20240921022957-49e7df575cb6/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= oras.land/oras-go v1.2.5 h1:XpYuAwAb0DfQsunIyMfeET92emK8km3W4yEzZvUbsTo= oras.land/oras-go v1.2.5/go.mod h1:PuAwRShRZCsZb7g8Ar3jKKQR/2A/qN+pkYxIOd/FAoo= sigs.k8s.io/controller-runtime v0.19.0 h1:nWVM7aq+Il2ABxwiCizrVDSlmDcshi9llbaFbC0ji/Q= sigs.k8s.io/controller-runtime v0.19.0/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= -sigs.k8s.io/gateway-api v1.2.0-rc1.0.20240923191000-5c5fc388829d h1:FZvkIACA+ke1SEbOiYyr3bfWr6QwJcrFYxEKyT6BAaQ= -sigs.k8s.io/gateway-api v1.2.0-rc1.0.20240923191000-5c5fc388829d/go.mod h1:EpNfEXNjiYfUJypf0eZ0P5iXA9ekSGWaS1WgPaM42X0= +sigs.k8s.io/gateway-api v1.2.0 h1:LrToiFwtqKTKZcZtoQPTuo3FxhrrhTgzQG0Te+YGSo8= +sigs.k8s.io/gateway-api v1.2.0/go.mod h1:EpNfEXNjiYfUJypf0eZ0P5iXA9ekSGWaS1WgPaM42X0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/kustomize/api v0.17.2 h1:E7/Fjk7V5fboiuijoZHgs4aHuexi5Y2loXlVOAVAG5g= sigs.k8s.io/kustomize/api v0.17.2/go.mod h1:UWTz9Ct+MvoeQsHcJ5e+vziRRkwimm3HytpZgIYqye0= sigs.k8s.io/kustomize/kyaml v0.17.1 h1:TnxYQxFXzbmNG6gOINgGWQt09GghzgTP6mIurOgrLCQ= sigs.k8s.io/kustomize/kyaml v0.17.1/go.mod h1:9V0mCjIEYjlXuCdYsSXvyoy2BTsLESH7TlGV81S282U= -sigs.k8s.io/mcs-api v0.1.1-0.20240919125245-7bbb5990134a h1:R2c2r4UrW0aDTc79y4MLTyDvhjdD+jAFtnjLLHDiDnc= -sigs.k8s.io/mcs-api v0.1.1-0.20240919125245-7bbb5990134a/go.mod h1:x0rgWQwGd3FJzrb94BNn3Nu7YxUwBWcgjVRbkrkVy2A= +sigs.k8s.io/mcs-api v0.1.1-0.20241002142749-eff1ba8c3ab2 h1:kYmFRW4FG7KvgoBRdvrlhFPScYu+ZKhVt+FBRl43CPE= +sigs.k8s.io/mcs-api v0.1.1-0.20241002142749-eff1ba8c3ab2/go.mod h1:x0rgWQwGd3FJzrb94BNn3Nu7YxUwBWcgjVRbkrkVy2A= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= diff --git a/vendor/github.com/cilium/charts/README.md b/vendor/github.com/cilium/charts/README.md index 88fea55229..fcf29bfcba 100644 --- a/vendor/github.com/cilium/charts/README.md +++ b/vendor/github.com/cilium/charts/README.md @@ -1,6 +1,8 @@ This repository holds helm templates for the following Cilium releases: +* [v1.17.0-pre.1](https://github.com/cilium/cilium/releases/tag/v1.17.0-pre.1) (_[source](https://github.com/cilium/cilium/tree/v1.17.0-pre.1/install/kubernetes/cilium)_) * [v1.17.0-pre.0](https://github.com/cilium/cilium/releases/tag/v1.17.0-pre.0) (_[source](https://github.com/cilium/cilium/tree/v1.17.0-pre.0/install/kubernetes/cilium)_) +* [v1.16.3](https://github.com/cilium/cilium/releases/tag/v1.16.3) (_[source](https://github.com/cilium/cilium/tree/v1.16.3/install/kubernetes/cilium)_) * [v1.16.2](https://github.com/cilium/cilium/releases/tag/v1.16.2) (_[source](https://github.com/cilium/cilium/tree/v1.16.2/install/kubernetes/cilium)_) * [v1.16.1](https://github.com/cilium/cilium/releases/tag/v1.16.1) (_[source](https://github.com/cilium/cilium/tree/v1.16.1/install/kubernetes/cilium)_) * [v1.16.0](https://github.com/cilium/cilium/releases/tag/v1.16.0) (_[source](https://github.com/cilium/cilium/tree/v1.16.0/install/kubernetes/cilium)_) @@ -11,6 +13,7 @@ This repository holds helm templates for the following Cilium releases: * [v1.16.0-pre.2](https://github.com/cilium/cilium/releases/tag/v1.16.0-pre.2) (_[source](https://github.com/cilium/cilium/tree/v1.16.0-pre.2/install/kubernetes/cilium)_) * [v1.16.0-pre.1](https://github.com/cilium/cilium/releases/tag/v1.16.0-pre.1) (_[source](https://github.com/cilium/cilium/tree/v1.16.0-pre.1/install/kubernetes/cilium)_) * [v1.16.0-pre.0](https://github.com/cilium/cilium/releases/tag/v1.16.0-pre.0) (_[source](https://github.com/cilium/cilium/tree/v1.16.0-pre.0/install/kubernetes/cilium)_) +* [v1.15.10](https://github.com/cilium/cilium/releases/tag/v1.15.10) (_[source](https://github.com/cilium/cilium/tree/v1.15.10/install/kubernetes/cilium)_) * [v1.15.9](https://github.com/cilium/cilium/releases/tag/v1.15.9) (_[source](https://github.com/cilium/cilium/tree/v1.15.9/install/kubernetes/cilium)_) * [v1.15.8](https://github.com/cilium/cilium/releases/tag/v1.15.8) (_[source](https://github.com/cilium/cilium/tree/v1.15.8/install/kubernetes/cilium)_) * [v1.15.7](https://github.com/cilium/cilium/releases/tag/v1.15.7) (_[source](https://github.com/cilium/cilium/tree/v1.15.7/install/kubernetes/cilium)_) @@ -27,6 +30,7 @@ This repository holds helm templates for the following Cilium releases: * [v1.15.0-pre.2](https://github.com/cilium/cilium/releases/tag/v1.15.0-pre.2) (_[source](https://github.com/cilium/cilium/tree/v1.15.0-pre.2/install/kubernetes/cilium)_) * [v1.15.0-pre.1](https://github.com/cilium/cilium/releases/tag/v1.15.0-pre.1) (_[source](https://github.com/cilium/cilium/tree/v1.15.0-pre.1/install/kubernetes/cilium)_) * [v1.15.0-pre.0](https://github.com/cilium/cilium/releases/tag/v1.15.0-pre.0) (_[source](https://github.com/cilium/cilium/tree/v1.15.0-pre.0/install/kubernetes/cilium)_) +* [v1.14.16](https://github.com/cilium/cilium/releases/tag/v1.14.16) (_[source](https://github.com/cilium/cilium/tree/v1.14.16/install/kubernetes/cilium)_) * [v1.14.15](https://github.com/cilium/cilium/releases/tag/v1.14.15) (_[source](https://github.com/cilium/cilium/tree/v1.14.15/install/kubernetes/cilium)_) * [v1.14.14](https://github.com/cilium/cilium/releases/tag/v1.14.14) (_[source](https://github.com/cilium/cilium/tree/v1.14.14/install/kubernetes/cilium)_) * [v1.14.13](https://github.com/cilium/cilium/releases/tag/v1.14.13) (_[source](https://github.com/cilium/cilium/tree/v1.14.13/install/kubernetes/cilium)_) diff --git a/vendor/github.com/cilium/charts/cilium-1.14.16.tgz b/vendor/github.com/cilium/charts/cilium-1.14.16.tgz new file mode 100644 index 0000000000000000000000000000000000000000..1cdc31637285d61e69ecf4cb15d20e13e6350afa GIT binary patch literal 165189 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 y*pYET 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ΝFysSd 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

%;br~ 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 M|K<+lyZ}TI0KZW^iU0rr literal 0 HcmV?d00001 diff --git a/vendor/github.com/cilium/charts/cilium-1.15.10.tgz b/vendor/github.com/cilium/charts/cilium-1.15.10.tgz new file mode 100644 index 0000000000000000000000000000000000000000..ed4c82fc52c2ee5f7a099e87375f60aae5dbd20d GIT binary patch literal 180660 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

I^8VR&If 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

87%vDq$Fk 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

iGH8F95`iwEydU~f~KJufCJCG3b}-G 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 G<`n=Dc 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

;HA%=43316D64ERYDT2AV@!-1 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

0~ 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 1.21.0-0' + name: cilium + sources: + - https://github.com/cilium/cilium + urls: + - cilium-1.17.0-pre.1.tgz + version: 1.17.0-pre.1 - annotations: artifacthub.io/crds: "- kind: CiliumNetworkPolicy\n version: v2\n name: ciliumnetworkpolicies.cilium.io\n \ displayName: Cilium Network Policy\n description: |\n Cilium Network @@ -107,6 +213,112 @@ entries: urls: - cilium-1.17.0-pre.0.tgz version: 1.17.0-pre.0 + - annotations: + artifacthub.io/crds: "- kind: CiliumNetworkPolicy\n version: v2\n name: ciliumnetworkpolicies.cilium.io\n + \ displayName: Cilium Network Policy\n description: |\n Cilium Network + Policies provide additional functionality beyond what\n is provided by + standard Kubernetes NetworkPolicy such as the ability\n to allow traffic + based on FQDNs, or to filter at Layer 7.\n- kind: CiliumClusterwideNetworkPolicy\n + \ version: v2\n name: ciliumclusterwidenetworkpolicies.cilium.io\n displayName: + Cilium Clusterwide Network Policy\n description: |\n Cilium Clusterwide + Network Policies support configuring network traffic\n policiies across + the entire cluster, including applying node firewalls.\n- kind: CiliumExternalWorkload\n + \ version: v2\n name: ciliumexternalworkloads.cilium.io\n displayName: Cilium + External Workload\n description: |\n Cilium External Workload supports + configuring the ability for external\n non-Kubernetes workloads to join + the cluster.\n- kind: CiliumLocalRedirectPolicy\n version: v2\n name: ciliumlocalredirectpolicies.cilium.io\n + \ displayName: Cilium Local Redirect Policy\n description: |\n Cilium + Local Redirect Policy allows local redirects to be configured\n within + a node to support use cases like Node-Local DNS or KIAM.\n- kind: CiliumNode\n + \ version: v2\n name: ciliumnodes.cilium.io\n displayName: Cilium Node\n + \ description: |\n Cilium Node represents a node managed by Cilium. It + contains a\n specification to control various node specific configuration + aspects\n and a status section to represent the status of the node.\n- + kind: CiliumIdentity\n version: v2\n name: ciliumidentities.cilium.io\n + \ displayName: Cilium Identity\n description: |\n Cilium Identity allows + introspection into security identities that\n Cilium allocates which identify + sets of labels that are assigned to\n individual endpoints in the cluster.\n- + kind: CiliumEndpoint\n version: v2\n name: ciliumendpoints.cilium.io\n displayName: + Cilium Endpoint\n description: |\n Cilium Endpoint represents the status + of individual pods or nodes in\n the cluster which are managed by Cilium, + including enforcement status,\n IP addressing and whether the networking + is successfully operational.\n- kind: CiliumEndpointSlice\n version: v2alpha1\n + \ name: ciliumendpointslices.cilium.io\n displayName: Cilium Endpoint Slice\n + \ description: |\n Cilium Endpoint Slice represents the status of groups + of pods or nodes\n in the cluster which are managed by Cilium, including + enforcement status,\n IP addressing and whether the networking is successfully + operational.\n- kind: CiliumEgressGatewayPolicy\n version: v2\n name: ciliumegressgatewaypolicies.cilium.io\n + \ displayName: Cilium Egress Gateway Policy\n description: |\n Cilium + Egress Gateway Policy provides control over the way that traffic\n leaves + the cluster and which source addresses to use for that traffic.\n- kind: CiliumClusterwideEnvoyConfig\n + \ version: v2\n name: ciliumclusterwideenvoyconfigs.cilium.io\n displayName: + Cilium Clusterwide Envoy Config\n description: |\n Cilium Clusterwide + Envoy Config specifies Envoy resources and K8s service mappings\n to be + provisioned into Cilium host proxy instances in cluster context.\n- kind: + CiliumEnvoyConfig\n version: v2\n name: ciliumenvoyconfigs.cilium.io\n displayName: + Cilium Envoy Config\n description: |\n Cilium Envoy Config specifies Envoy + resources and K8s service mappings\n to be provisioned into Cilium host + proxy instances in namespace context.\n- kind: CiliumBGPPeeringPolicy\n version: + v2alpha1\n name: ciliumbgppeeringpolicies.cilium.io\n displayName: Cilium + BGP Peering Policy\n description: |\n Cilium BGP Peering Policy instructs + Cilium to create specific BGP peering\n configurations.\n- kind: CiliumBGPClusterConfig\n + \ version: v2alpha1\n name: ciliumbgpclusterconfigs.cilium.io\n displayName: + Cilium BGP Cluster Config\n description: |\n Cilium BGP Cluster Config + instructs Cilium operator to create specific BGP cluster\n configurations.\n- + kind: CiliumBGPPeerConfig\n version: v2alpha1\n name: ciliumbgppeerconfigs.cilium.io\n + \ displayName: Cilium BGP Peer Config\n description: |\n CiliumBGPPeerConfig + is a common set of BGP peer configurations. It can be referenced \n by + multiple peers from CiliumBGPClusterConfig.\n- kind: CiliumBGPAdvertisement\n + \ version: v2alpha1\n name: ciliumbgpadvertisements.cilium.io\n displayName: + Cilium BGP Advertisement\n description: |\n CiliumBGPAdvertisement is + used to define source of BGP advertisement as well as BGP attributes \n to + be advertised with those prefixes.\n- kind: CiliumBGPNodeConfig\n version: + v2alpha1\n name: ciliumbgpnodeconfigs.cilium.io\n displayName: Cilium BGP + Node Config\n description: |\n CiliumBGPNodeConfig is read only node specific + BGP configuration. It is constructed by Cilium operator.\n It will also + contain node local BGP state information.\n- kind: CiliumBGPNodeConfigOverride\n + \ version: v2alpha1\n name: ciliumbgpnodeconfigoverrides.cilium.io\n displayName: + Cilium BGP Node Config Override\n description: |\n CiliumBGPNodeConfigOverride + can be used to override node specific BGP configuration.\n- kind: CiliumLoadBalancerIPPool\n + \ version: v2alpha1\n name: ciliumloadbalancerippools.cilium.io\n displayName: + Cilium Load Balancer IP Pool\n description: |\n Defining a Cilium Load + Balancer IP Pool instructs Cilium to assign IPs to LoadBalancer Services.\n- + kind: CiliumNodeConfig\n version: v2alpha1\n name: ciliumnodeconfigs.cilium.io\n + \ displayName: Cilium Node Configuration\n description: |\n CiliumNodeConfig + is a list of configuration key-value pairs. It is applied to\n nodes indicated + by a label selector.\n- kind: CiliumCIDRGroup\n version: v2alpha1\n name: + ciliumcidrgroups.cilium.io\n displayName: Cilium CIDR Group\n description: + |\n CiliumCIDRGroup is a list of CIDRs that can be referenced as a single + entity from CiliumNetworkPolicies.\n- kind: CiliumL2AnnouncementPolicy\n version: + v2alpha1\n name: ciliuml2announcementpolicies.cilium.io\n displayName: Cilium + L2 Announcement Policy\n description: |\n CiliumL2AnnouncementPolicy is + a policy which determines which service IPs will be announced to\n the + local area network, by which nodes, and via which interfaces.\n- kind: CiliumPodIPPool\n + \ version: v2alpha1\n name: ciliumpodippools.cilium.io\n displayName: Cilium + Pod IP Pool\n description: |\n CiliumPodIPPool defines an IP pool that + can be used for pooled IPAM (i.e. the multi-pool IPAM mode).\n" + apiVersion: v2 + appVersion: 1.16.3 + created: "2024-10-11T23:02:59.494005644Z" + description: eBPF-based Networking, Security, and Observability + digest: e1be328218c74bd2bed91f996d8c1b10e785715ce53299a392f79b0cef796805 + home: https://cilium.io/ + icon: https://cdn.jsdelivr.net/gh/cilium/cilium@main/Documentation/images/logo-solo.svg + keywords: + - BPF + - eBPF + - Kubernetes + - Networking + - Security + - Observability + - Troubleshooting + kubeVersion: '>= 1.21.0-0' + name: cilium + sources: + - https://github.com/cilium/cilium + urls: + - cilium-1.16.3.tgz + version: 1.16.3 - annotations: artifacthub.io/crds: "- kind: CiliumNetworkPolicy\n version: v2\n name: ciliumnetworkpolicies.cilium.io\n \ displayName: Cilium Network Policy\n description: |\n Cilium Network @@ -1273,6 +1485,112 @@ entries: urls: - cilium-1.16.0-dev.tgz version: 1.16.0-dev + - annotations: + artifacthub.io/crds: "- kind: CiliumNetworkPolicy\n version: v2\n name: ciliumnetworkpolicies.cilium.io\n + \ displayName: Cilium Network Policy\n description: |\n Cilium Network + Policies provide additional functionality beyond what\n is provided by + standard Kubernetes NetworkPolicy such as the ability\n to allow traffic + based on FQDNs, or to filter at Layer 7.\n- kind: CiliumClusterwideNetworkPolicy\n + \ version: v2\n name: ciliumclusterwidenetworkpolicies.cilium.io\n displayName: + Cilium Clusterwide Network Policy\n description: |\n Cilium Clusterwide + Network Policies support configuring network traffic\n policiies across + the entire cluster, including applying node firewalls.\n- kind: CiliumExternalWorkload\n + \ version: v2\n name: ciliumexternalworkloads.cilium.io\n displayName: Cilium + External Workload\n description: |\n Cilium External Workload supports + configuring the ability for external\n non-Kubernetes workloads to join + the cluster.\n- kind: CiliumLocalRedirectPolicy\n version: v2\n name: ciliumlocalredirectpolicies.cilium.io\n + \ displayName: Cilium Local Redirect Policy\n description: |\n Cilium + Local Redirect Policy allows local redirects to be configured\n within + a node to support use cases like Node-Local DNS or KIAM.\n- kind: CiliumNode\n + \ version: v2\n name: ciliumnodes.cilium.io\n displayName: Cilium Node\n + \ description: |\n Cilium Node represents a node managed by Cilium. It + contains a\n specification to control various node specific configuration + aspects\n and a status section to represent the status of the node.\n- + kind: CiliumIdentity\n version: v2\n name: ciliumidentities.cilium.io\n + \ displayName: Cilium Identity\n description: |\n Cilium Identity allows + introspection into security identities that\n Cilium allocates which identify + sets of labels that are assigned to\n individual endpoints in the cluster.\n- + kind: CiliumEndpoint\n version: v2\n name: ciliumendpoints.cilium.io\n displayName: + Cilium Endpoint\n description: |\n Cilium Endpoint represents the status + of individual pods or nodes in\n the cluster which are managed by Cilium, + including enforcement status,\n IP addressing and whether the networking + is successfully operational.\n- kind: CiliumEndpointSlice\n version: v2alpha1\n + \ name: ciliumendpointslices.cilium.io\n displayName: Cilium Endpoint Slice\n + \ description: |\n Cilium Endpoint Slice represents the status of groups + of pods or nodes\n in the cluster which are managed by Cilium, including + enforcement status,\n IP addressing and whether the networking is successfully + operational.\n- kind: CiliumEgressGatewayPolicy\n version: v2\n name: ciliumegressgatewaypolicies.cilium.io\n + \ displayName: Cilium Egress Gateway Policy\n description: |\n Cilium + Egress Gateway Policy provides control over the way that traffic\n leaves + the cluster and which source addresses to use for that traffic.\n- kind: CiliumClusterwideEnvoyConfig\n + \ version: v2\n name: ciliumclusterwideenvoyconfigs.cilium.io\n displayName: + Cilium Clusterwide Envoy Config\n description: |\n Cilium Clusterwide + Envoy Config specifies Envoy resources and K8s service mappings\n to be + provisioned into Cilium host proxy instances in cluster context.\n- kind: + CiliumEnvoyConfig\n version: v2\n name: ciliumenvoyconfigs.cilium.io\n displayName: + Cilium Envoy Config\n description: |\n Cilium Envoy Config specifies Envoy + resources and K8s service mappings\n to be provisioned into Cilium host + proxy instances in namespace context.\n- kind: CiliumBGPPeeringPolicy\n version: + v2alpha1\n name: ciliumbgppeeringpolicies.cilium.io\n displayName: Cilium + BGP Peering Policy\n description: |\n Cilium BGP Peering Policy instructs + Cilium to create specific BGP peering\n configurations.\n- kind: CiliumBGPClusterConfig\n + \ version: v2alpha1\n name: ciliumbgpclusterconfigs.cilium.io\n displayName: + Cilium BGP Cluster Config\n description: |\n Cilium BGP Cluster Config + instructs Cilium operator to create specific BGP cluster\n configurations.\n- + kind: CiliumBGPPeerConfig\n version: v2alpha1\n name: ciliumbgppeerconfigs.cilium.io\n + \ displayName: Cilium BGP Peer Config\n description: |\n CiliumBGPPeerConfig + is a common set of BGP peer configurations. It can be referenced \n by + multiple peers from CiliumBGPClusterConfig.\n- kind: CiliumBGPAdvertisement\n + \ version: v2alpha1\n name: ciliumbgpadvertisements.cilium.io\n displayName: + Cilium BGP Advertisement\n description: |\n CiliumBGPAdvertisement is + used to define source of BGP advertisement as well as BGP attributes \n to + be advertised with those prefixes.\n- kind: CiliumBGPNodeConfig\n version: + v2alpha1\n name: ciliumbgpnodeconfigs.cilium.io\n displayName: Cilium BGP + Node Config\n description: |\n CiliumBGPNodeConfig is read only node specific + BGP configuration. It is constructed by Cilium operator.\n It will also + contain node local BGP state information.\n- kind: CiliumBGPNodeConfigOverride\n + \ version: v2alpha1\n name: ciliumbgpnodeconfigoverrides.cilium.io\n displayName: + Cilium BGP Node Config Override\n description: |\n CiliumBGPNodeConfigOverride + can be used to override node specific BGP configuration.\n- kind: CiliumLoadBalancerIPPool\n + \ version: v2alpha1\n name: ciliumloadbalancerippools.cilium.io\n displayName: + Cilium Load Balancer IP Pool\n description: |\n Defining a Cilium Load + Balancer IP Pool instructs Cilium to assign IPs to LoadBalancer Services.\n- + kind: CiliumNodeConfig\n version: v2alpha1\n name: ciliumnodeconfigs.cilium.io\n + \ displayName: Cilium Node Configuration\n description: |\n CiliumNodeConfig + is a list of configuration key-value pairs. It is applied to\n nodes indicated + by a label selector.\n- kind: CiliumCIDRGroup\n version: v2alpha1\n name: + ciliumcidrgroups.cilium.io\n displayName: Cilium CIDR Group\n description: + |\n CiliumCIDRGroup is a list of CIDRs that can be referenced as a single + entity from CiliumNetworkPolicies.\n- kind: CiliumL2AnnouncementPolicy\n version: + v2alpha1\n name: ciliuml2announcementpolicies.cilium.io\n displayName: Cilium + L2 Announcement Policy\n description: |\n CiliumL2AnnouncementPolicy is + a policy which determines which service IPs will be announced to\n the + local area network, by which nodes, and via which interfaces.\n- kind: CiliumPodIPPool\n + \ version: v2alpha1\n name: ciliumpodippools.cilium.io\n displayName: Cilium + Pod IP Pool\n description: |\n CiliumPodIPPool defines an IP pool that + can be used for pooled IPAM (i.e. the multi-pool IPAM mode).\n" + apiVersion: v2 + appVersion: 1.15.10 + created: "2024-10-11T23:00:04.066907249Z" + description: eBPF-based Networking, Security, and Observability + digest: c8bbcb8d5a7e566c05a9e827108f97b5baa9f74e28f16d551a983d42a57ff94f + home: https://cilium.io/ + icon: https://cdn.jsdelivr.net/gh/cilium/cilium@main/Documentation/images/logo-solo.svg + keywords: + - BPF + - eBPF + - Kubernetes + - Networking + - Security + - Observability + - Troubleshooting + kubeVersion: '>= 1.16.0-0' + name: cilium + sources: + - https://github.com/cilium/cilium + urls: + - cilium-1.15.10.tgz + version: 1.15.10 - annotations: artifacthub.io/crds: "- kind: CiliumNetworkPolicy\n version: v2\n name: ciliumnetworkpolicies.cilium.io\n \ displayName: Cilium Network Policy\n description: |\n Cilium Network @@ -3171,6 +3489,151 @@ entries: description: | CiliumPodIPPool defines an IP pool that can be used for pooled IPAM (i.e. the multi-pool IPAM mode). apiVersion: v2 + appVersion: 1.14.16 + created: "2024-10-11T21:38:37.489766156Z" + description: eBPF-based Networking, Security, and Observability + digest: b6b755176cb61d31b32d5107bedffef51b1d2936d6de5eed6036815fca6834e4 + home: https://cilium.io/ + icon: https://cdn.jsdelivr.net/gh/cilium/cilium@main/Documentation/images/logo-solo.svg + keywords: + - BPF + - eBPF + - Kubernetes + - Networking + - Security + - Observability + - Troubleshooting + kubeVersion: '>= 1.16.0-0' + name: cilium + sources: + - https://github.com/cilium/cilium + urls: + - cilium-1.14.16.tgz + version: 1.14.16 + - annotations: + artifacthub.io/crds: | + - kind: CiliumNetworkPolicy + version: v2 + name: ciliumnetworkpolicies.cilium.io + displayName: Cilium Network Policy + description: | + Cilium Network Policies provide additional functionality beyond what + is provided by standard Kubernetes NetworkPolicy such as the ability + to allow traffic based on FQDNs, or to filter at Layer 7. + - kind: CiliumClusterwideNetworkPolicy + version: v2 + name: ciliumclusterwidenetworkpolicies.cilium.io + displayName: Cilium Clusterwide Network Policy + description: | + Cilium Clusterwide Network Policies support configuring network traffic + policiies across the entire cluster, including applying node firewalls. + - kind: CiliumExternalWorkload + version: v2 + name: ciliumexternalworkloads.cilium.io + displayName: Cilium External Workload + description: | + Cilium External Workload supports configuring the ability for external + non-Kubernetes workloads to join the cluster. + - kind: CiliumLocalRedirectPolicy + version: v2 + name: ciliumlocalredirectpolicies.cilium.io + displayName: Cilium Local Redirect Policy + description: | + Cilium Local Redirect Policy allows local redirects to be configured + within a node to support use cases like Node-Local DNS or KIAM. + - kind: CiliumNode + version: v2 + name: ciliumnodes.cilium.io + displayName: Cilium Node + description: | + Cilium Node represents a node managed by Cilium. It contains a + specification to control various node specific configuration aspects + and a status section to represent the status of the node. + - kind: CiliumIdentity + version: v2 + name: ciliumidentities.cilium.io + displayName: Cilium Identity + description: | + Cilium Identity allows introspection into security identities that + Cilium allocates which identify sets of labels that are assigned to + individual endpoints in the cluster. + - kind: CiliumEndpoint + version: v2 + name: ciliumendpoints.cilium.io + displayName: Cilium Endpoint + description: | + Cilium Endpoint represents the status of individual pods or nodes in + the cluster which are managed by Cilium, including enforcement status, + IP addressing and whether the networking is succesfully operational. + - kind: CiliumEndpointSlice + version: v2alpha1 + name: ciliumendpointslices.cilium.io + displayName: Cilium Endpoint Slice + description: | + Cilium Endpoint Slice represents the status of groups of pods or nodes + in the cluster which are managed by Cilium, including enforcement status, + IP addressing and whether the networking is succesfully operational. + - kind: CiliumEgressGatewayPolicy + version: v2 + name: ciliumegressgatewaypolicies.cilium.io + displayName: Cilium Egress Gateway Policy + description: | + Cilium Egress Gateway Policy provides control over the way that traffic + leaves the cluster and which source addresses to use for that traffic. + - kind: CiliumClusterwideEnvoyConfig + version: v2 + name: ciliumclusterwideenvoyconfigs.cilium.io + displayName: Cilium Clusterwide Envoy Config + description: | + Cilium Clusterwide Envoy Config specifies Envoy resources and K8s service mappings + to be provisioned into Cilium host proxy instances in cluster context. + - kind: CiliumEnvoyConfig + version: v2 + name: ciliumenvoyconfigs.cilium.io + displayName: Cilium Envoy Config + description: | + Cilium Envoy Config specifies Envoy resources and K8s service mappings + to be provisioned into Cilium host proxy instances in namespace context. + - kind: CiliumBGPPeeringPolicy + version: v2alpha1 + name: ciliumbgppeeringpolicies.cilium.io + displayName: Cilium BGP Peering Policy + description: | + Cilium BGP Peering Policy instructs Cilium to create specific BGP peering + configurations. + - kind: CiliumLoadBalancerIPPool + version: v2alpha1 + name: ciliumloadbalancerippools.cilium.io + displayName: Cilium Load Balancer IP Pool + description: | + Defining a Cilium Load Balancer IP Pool instructs Cilium to assign IPs to LoadBalancer Services. + - kind: CiliumNodeConfig + version: v2alpha1 + name: ciliumnodeconfigs.cilium.io + displayName: Cilium Node Configuration + description: | + CiliumNodeConfig is a list of configuration key-value pairs. It is applied to + nodes indicated by a label selector. + - kind: CiliumCIDRGroup + version: v2alpha1 + name: ciliumcidrgroups.cilium.io + displayName: Cilium CIDR Group + description: | + CiliumCIDRGroup is a list of CIDRs that can be referenced as a single entity from CiliumNetworkPolicies. + - kind: CiliumL2AnnouncementPolicy + version: v2alpha1 + name: ciliuml2announcementpolicies.cilium.io + displayName: Cilium L2 Announcement Policy + description: | + CiliumL2AnnouncementPolicy is a policy which determines which service IPs will be announced to + the local area network, by which nodes, and via which interfaces. + - kind: CiliumPodIPPool + version: v2alpha1 + name: ciliumpodippools.cilium.io + displayName: Cilium Pod IP Pool + description: | + CiliumPodIPPool defines an IP pool that can be used for pooled IPAM (i.e. the multi-pool IPAM mode). + apiVersion: v2 appVersion: 1.14.15 created: "2024-09-26T11:36:56.83422162Z" description: eBPF-based Networking, Security, and Observability @@ -20091,4 +20554,4 @@ entries: urls: - tetragon-0.8.0.tgz version: 0.8.0 -generated: "2024-09-26T11:51:56.299230787Z" +generated: "2024-10-11T23:02:59.483940884Z" diff --git a/vendor/github.com/cilium/cilium/AUTHORS b/vendor/github.com/cilium/cilium/AUTHORS index 58d66d52de..77b10f4780 100644 --- a/vendor/github.com/cilium/cilium/AUTHORS +++ b/vendor/github.com/cilium/cilium/AUTHORS @@ -13,6 +13,7 @@ Aditi Ghag aditi@cilium.io Aditya Kumar aditya.kumar60@infosys.com Aditya Purandare aditya.p1993@hotmail.com Aditya Sharma aditya.sharma@shopify.com +Adrian Berger adrian.berger@bedag.ch Adrien Trouillaud adrienjt@users.noreply.github.com Ahmed Bebars 1381372+abebars@users.noreply.github.com Akhil Velagapudi 4@4khil.com @@ -25,6 +26,7 @@ Alexander Alemayhu alexander@alemayhu.com Alexander Berger alex-berger@gmx.ch Alexander Block ablock84@gmail.com Alexander Demichev demichev.alexander@gmail.com +Alexandre Barone abalexandrebarone@gmail.com Alexandre Perrin alex@isovalent.com Alexei Starovoitov alexei.starovoitov@gmail.com Alexey Grevtsev alexey.grevtcev@gmail.com @@ -32,6 +34,7 @@ Alex Katsman alexkats@google.com Alex Romanov alex@romanov.ws Alex Szakaly alex.szakaly@gmail.com Alex Waring alex.waring@starlingbank.com +alisdairbr alisdairbr@users.noreply.github.com Alkama Hasan gl3118@myamu.ac.in Alois Petutschnig alois@petutschnig.net Alvaro Uria alvaro.uria@isovalent.com @@ -43,6 +46,7 @@ Amre Shakimov amre@covalent.io Anderson, David L david.l.anderson@intel.com Andor Nemeth andor_nemeth@swissre.com Andreas Mårtensson andreas@addem.se +André Costa ancosta@gmail.com Andree Klattenhoff mail@andr.ee Andrei Kvapil kvapss@gmail.com André Martins andre@cilium.io @@ -56,7 +60,7 @@ Andrey Devyatkin andrey.devyatkin@fivexl.io Andrey Klimentyev andrey.klimentyev@flant.com Andrey Maltsev maltsev.andrey@gmail.com Andrey Voronkov voronkovaa@gmail.com -Andrii Iuspin andrii.iuspin@isovalent.com +Andrii Iuspin 57713382+ayuspin@users.noreply.github.com Andrzej Mamak nqaegg@gmail.com Andy Allred andy@punasusi.com andychuang andy.chuang@shoplineapp.com @@ -83,8 +87,10 @@ Archana Shinde archana.m.shinde@intel.com Archer Wu archerwu9425@icloud.com Ardika Bagus me@ardikabs.com Arika Chen eaglesora@gmail.com +Arkadiusz Kaliwoda (akaliwod) akaliwod@cisco.com Arnaud Meukam ameukam@gmail.com Arseniy Belorukov a.belorukov@team.bumble.com +Artem Tokarev enjoy1288@gmail.com Arthur Chiao arthurchiao@hotmail.com ArthurChiao arthurchiao@hotmail.com Arthur Evstifeev mail@ap4y.me @@ -180,6 +186,7 @@ Cory Snyder csnyder@1111systems.com Craig Box craig.box@gmail.com crashiura crashiura@gmail.com cui fliter imcusg@gmail.com +cx 1249843194@qq.com Cynthia Thomas cynthia@covalent.io Cyril Corbon corboncyril@gmail.com Cyril Scetbon cscetbon@gmail.com @@ -258,6 +265,7 @@ Dylan Reimerink dylan.reimerink@isovalent.com Ekene Nwobodo nwobodoe71@gmail.com Electron alokaks601@gmail.com El-Fadel Bonfoh elfadel@accuknox.com +Elias Hernandez elirayhernandez@gmail.com eliranw 39266788+eliranw@users.noreply.github.com Ellie Springsteen ellie.springsteen@appian.com Eloy Coto eloy.coto@acalustra.com @@ -411,6 +419,7 @@ John Fastabend john.fastabend@gmail.com John Gardiner Myers jgmyers@proofpoint.com John Howard howardjohn@google.com John Karoyannis karoyannis@yahoo.com +john-r-swyftx john.roche@swyftx.com.au John Watson johnw@planetscale.com John Zheng johnzhengaz@gmail.com Jomen Xiao jomenxiao@gmail.com @@ -426,6 +435,7 @@ Joseph-Irving joseph.irving500@gmail.com Joseph Ligier joseph.ligier@accenture.com Joseph Sheng jiajun.sheng@microfocus.com Joseph Stevens thejosephstevens@gmail.com +Josh Soref 2119212+jsoref@users.noreply.github.com joshua 54235339+sujoshua@users.noreply.github.com Joshua Roppo joshroppo@gmail.com jshr-w shjayaraman@microsoft.com @@ -550,6 +560,7 @@ Matt Anderson matanderson@equinix.com Matthew Fenwick mfenwick100@gmail.com Matthew Gumport me@gum.pt Matthew Hembree 47449406+matthewhembree@users.noreply.github.com +Matthias Baur m.baur@syseleven.de Matthieu Antoine matthieu.antoine@jumo.world Matthieu MOREL matthieu.morel35@gmail.com Matt Layher mdlayher@gmail.com @@ -613,6 +624,7 @@ Nick Young nick@isovalent.com Niclas Mietz solidnerd@users.noreply.github.com Nico Berlee nico.berlee@on2it.net Nicolas Busseneau nicolas@isovalent.com +Nicolò Ciraci ciraci.nicolo@gmail.com Nico Vibert nicolas.vibert@isovalent.com Nikhil Jha nikhiljha@users.noreply.github.com Nikhil Sharma nikhilsharma230303@gmail.com @@ -639,9 +651,10 @@ Oliver Wang a0924100192@gmail.com Omar Aloraini ooraini.dev@gmail.com Ondrej Blazek ondrej.blazek@firma.seznam.cz Ondrej Sika ondrej@ondrejsika.com +oneumyvakin oneumyvaking@mail.ru Oshan Galwaduge oshan304@gmail.com Osthues osthues.matthias@gmail.com -Ovidiu Tirla ovi2022@gmail.com +Ovidiu Tirla otirla@google.com Pablo Ruiz pablo.ruiz@gmail.com Paco Xu paco.xu@daocloud.io Parth Patel parth.psu@gmail.com @@ -660,11 +673,13 @@ Paulo Gomes pjbgf@linux.com Pavel Pavlov 40396270+PavelPavlov46@users.noreply.github.com Pavel Tishkov pavel.tishkov@flant.com Paweł Prażak pawelprazak@users.noreply.github.com +Pedro Ignacio pedroig100.pi@gmail.com Peiqi Shi uestc.shi@gmail.com Pelle van Gils pelle@vangils.dev pengbinbin1 pengbiny@163.com Pengfei Song pengfei.song@daocloud.io Peter Jausovec peter.jausovec@solo.io +Peter Matulis pmatulis@gmail.com Peter Slovak slovak.peto@gmail.com Petr Baloun petr.baloun@firma.seznam.cz Philippe Lafoucrière philippe.lafoucriere@gmail.com @@ -672,6 +687,7 @@ Philipp Gniewosz philipp.gniewosz@daimlertruck.com Philip Schmid phisch@cisco.com Pierre-Yves Aillet pyaillet@gmail.com Pieter van der Giessen pieter@pionative.com +Pooja Trivedi poojatrivedi@gmail.com Prabhakhar Kaliyamurthy (PK) prabhakhar@gmail.com Pranavi Roy pranvyr@gmail.com Prashanth.B beeps@google.com @@ -682,6 +698,7 @@ Priya Sharma Priya.Sharma6693@gmail.com Qasim Sarfraz qasim.sarfraz@esailors.de Qifeng Guo qifeng.guo@daocloud.io Qingchuan Hao qinhao@microsoft.com +Quan Wei quanwei.153@bytedance.com Quentin Monnet qmo@qmon.net Raam ram29@bskyb.com Rachid Zarouali rachid.zarouali@sevensphere.io @@ -727,12 +744,15 @@ Roman Ptitcyn romanspb@yahoo.com Romuald Zdebskiy zdebskiy@hotmail.com Ronald van Zantvoort the.loeki@gmail.com Ross Guarino rssguar@gmail.com +roykharman roykharman@gmail.com Rui Chen rui@chenrui.dev Rui Gu rui@covalent.io Rushikesh Butley rushikeshbutley@gmail.com Russell Bryant russell@russellbryant.net +rusttech gopher@before.tech Ryan Drew ryan.drew@isovalent.com Ryan McNamara rmcnamara@palantir.com +ryebridge 88094554+ryebridge@users.noreply.github.com Sachin Maurya sachin.maurya7666@gmail.com Sadik Kuzu sadikkuzu@hotmail.com Sahid Orentino Ferdjaoui sahid.ferdjaoui@industrialdiscipline.com @@ -773,6 +793,7 @@ Simone Magnani simone.magnani@isovalent.com Simone Sciarrati s.sciarrati@gmail.com Simon Felding 45149055+simonfelding@users.noreply.github.com Simon Gerber simon.gerber@vshn.ch +Simon Lackerbauer mail@ciil.io Simon Pasquier spasquier@mirantis.com Sjouke de Vries info@sdvservices.nl SkalaNetworks contact@skala.network @@ -781,6 +802,7 @@ Smaine Kahlouch smainklh@gmail.com soggiest nicholas@isovalent.com Song 1120344670@qq.com spacewander spacewanderlzx@gmail.com +Sridhar K N Rao sridharkn@u.nus.edu ssttehrani ssttehrani@gmail.com Stacy Kim stacy.kim@ucla.edu Stefan Zwanenburg stefan@zwanenburg.info @@ -816,11 +838,13 @@ Thiago Navarro navarro@accuknox.com Thi Van Le vannnyle@gmail.com Thomas Bachman tbachman@yahoo.com Thomas Balthazar thomas@balthazar.info +thomas.chen thomas.chen@trustasia.com Thomas Gosteli thomas.gosteli@protonmail.com Thomas Graf thomas@cilium.io Thorben von Hacht tvonhacht@apple.com Thorsten Pfister thorsten.pfister@form3.tech tigerK yanru.lv@daocloud.io +Tilusch til.heini@swisscom.com Tim Horner timothy.horner@isovalent.com Timo Beckers timo@isovalent.com Timo Reimann ttr314@googlemail.com @@ -842,6 +866,7 @@ Tony Lu tonylu@linux.alibaba.com Tony Norlin tony.norlin@localdomain.se Torben Tretau torben@tretau.net Tore S. Loenoey tore.lonoy@gmail.com +ToroNZ tomas-github@maggio.nz toVersus toversus2357@gmail.com Travis Glenn Hansen travisghansen@yahoo.com Trevor Roberts Jr Trevor.Roberts.Jr@gmail.com @@ -856,6 +881,7 @@ Vadim Ponomarev velizarx@gmail.com vakr vakr@microsoft.com Valas Valancius valas@google.com Vance Li vanceli@tencent.com +Vanilla osu_Vanilla@126.com Vigneshwaren Sunder vickymailed@gmail.com Viktor Kurchenko viktor.kurchenko@isovalent.com Viktor Kuzmin kvaster@gmail.com diff --git a/vendor/github.com/cilium/cilium/api/v1/flow/flow.pb.go b/vendor/github.com/cilium/cilium/api/v1/flow/flow.pb.go index bedbb29a45..8dfda1ba61 100644 --- a/vendor/github.com/cilium/cilium/api/v1/flow/flow.pb.go +++ b/vendor/github.com/cilium/cilium/api/v1/flow/flow.pb.go @@ -3,8 +3,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 -// protoc v5.28.2 +// protoc-gen-go v1.35.1 +// protoc v5.28.3 // source: flow/flow.proto package flow @@ -1475,11 +1475,9 @@ type Flow struct { func (x *Flow) Reset() { *x = Flow{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Flow) String() string { @@ -1490,7 +1488,7 @@ func (*Flow) ProtoMessage() {} func (x *Flow) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1799,11 +1797,9 @@ type FileInfo struct { func (x *FileInfo) Reset() { *x = FileInfo{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *FileInfo) String() string { @@ -1814,7 +1810,7 @@ func (*FileInfo) ProtoMessage() {} func (x *FileInfo) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1860,11 +1856,9 @@ type Layer4 struct { func (x *Layer4) Reset() { *x = Layer4{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Layer4) String() string { @@ -1875,7 +1869,7 @@ func (*Layer4) ProtoMessage() {} func (x *Layer4) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1988,11 +1982,9 @@ type Layer7 struct { func (x *Layer7) Reset() { *x = Layer7{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Layer7) String() string { @@ -2003,7 +1995,7 @@ func (*Layer7) ProtoMessage() {} func (x *Layer7) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2096,11 +2088,9 @@ type TraceContext struct { func (x *TraceContext) Reset() { *x = TraceContext{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *TraceContext) String() string { @@ -2111,7 +2101,7 @@ func (*TraceContext) ProtoMessage() {} func (x *TraceContext) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2146,11 +2136,9 @@ type TraceParent struct { func (x *TraceParent) Reset() { *x = TraceParent{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *TraceParent) String() string { @@ -2161,7 +2149,7 @@ func (*TraceParent) ProtoMessage() {} func (x *TraceParent) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2200,11 +2188,9 @@ type Endpoint struct { func (x *Endpoint) Reset() { *x = Endpoint{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Endpoint) String() string { @@ -2215,7 +2201,7 @@ func (*Endpoint) ProtoMessage() {} func (x *Endpoint) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2290,11 +2276,9 @@ type Workload struct { func (x *Workload) Reset() { *x = Workload{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Workload) String() string { @@ -2305,7 +2289,7 @@ func (*Workload) ProtoMessage() {} func (x *Workload) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2346,11 +2330,9 @@ type TCP struct { func (x *TCP) Reset() { *x = TCP{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *TCP) String() string { @@ -2361,7 +2343,7 @@ func (*TCP) ProtoMessage() {} func (x *TCP) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[8] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2415,11 +2397,9 @@ type IP struct { func (x *IP) Reset() { *x = IP{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *IP) String() string { @@ -2430,7 +2410,7 @@ func (*IP) ProtoMessage() {} func (x *IP) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[9] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2491,11 +2471,9 @@ type Ethernet struct { func (x *Ethernet) Reset() { *x = Ethernet{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Ethernet) String() string { @@ -2506,7 +2484,7 @@ func (*Ethernet) ProtoMessage() {} func (x *Ethernet) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[10] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2553,11 +2531,9 @@ type TCPFlags struct { func (x *TCPFlags) Reset() { *x = TCPFlags{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *TCPFlags) String() string { @@ -2568,7 +2544,7 @@ func (*TCPFlags) ProtoMessage() {} func (x *TCPFlags) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[11] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2657,11 +2633,9 @@ type UDP struct { func (x *UDP) Reset() { *x = UDP{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *UDP) String() string { @@ -2672,7 +2646,7 @@ func (*UDP) ProtoMessage() {} func (x *UDP) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[12] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2712,11 +2686,9 @@ type SCTP struct { func (x *SCTP) Reset() { *x = SCTP{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[13] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *SCTP) String() string { @@ -2727,7 +2699,7 @@ func (*SCTP) ProtoMessage() {} func (x *SCTP) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[13] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2767,11 +2739,9 @@ type ICMPv4 struct { func (x *ICMPv4) Reset() { *x = ICMPv4{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[14] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ICMPv4) String() string { @@ -2782,7 +2752,7 @@ func (*ICMPv4) ProtoMessage() {} func (x *ICMPv4) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[14] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2822,11 +2792,9 @@ type ICMPv6 struct { func (x *ICMPv6) Reset() { *x = ICMPv6{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[15] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ICMPv6) String() string { @@ -2837,7 +2805,7 @@ func (*ICMPv6) ProtoMessage() {} func (x *ICMPv6) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[15] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2880,11 +2848,9 @@ type Policy struct { func (x *Policy) Reset() { *x = Policy{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[16] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Policy) String() string { @@ -2895,7 +2861,7 @@ func (*Policy) ProtoMessage() {} func (x *Policy) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[16] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2964,11 +2930,9 @@ type EventTypeFilter struct { func (x *EventTypeFilter) Reset() { *x = EventTypeFilter{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[17] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *EventTypeFilter) String() string { @@ -2979,7 +2943,7 @@ func (*EventTypeFilter) ProtoMessage() {} func (x *EventTypeFilter) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[17] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3033,11 +2997,9 @@ type CiliumEventType struct { func (x *CiliumEventType) Reset() { *x = CiliumEventType{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[18] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *CiliumEventType) String() string { @@ -3048,7 +3010,7 @@ func (*CiliumEventType) ProtoMessage() {} func (x *CiliumEventType) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[18] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3180,11 +3142,9 @@ type FlowFilter struct { func (x *FlowFilter) Reset() { *x = FlowFilter{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[19] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *FlowFilter) String() string { @@ -3195,7 +3155,7 @@ func (*FlowFilter) ProtoMessage() {} func (x *FlowFilter) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[19] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3502,11 +3462,9 @@ type DNS struct { func (x *DNS) Reset() { *x = DNS{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[20] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *DNS) String() string { @@ -3517,7 +3475,7 @@ func (*DNS) ProtoMessage() {} func (x *DNS) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[20] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3599,11 +3557,9 @@ type HTTPHeader struct { func (x *HTTPHeader) Reset() { *x = HTTPHeader{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[21] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *HTTPHeader) String() string { @@ -3614,7 +3570,7 @@ func (*HTTPHeader) ProtoMessage() {} func (x *HTTPHeader) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[21] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3658,11 +3614,9 @@ type HTTP struct { func (x *HTTP) Reset() { *x = HTTP{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[22] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *HTTP) String() string { @@ -3673,7 +3627,7 @@ func (*HTTP) ProtoMessage() {} func (x *HTTP) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[22] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3738,11 +3692,9 @@ type Kafka struct { func (x *Kafka) Reset() { *x = Kafka{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[23] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Kafka) String() string { @@ -3753,7 +3705,7 @@ func (*Kafka) ProtoMessage() {} func (x *Kafka) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[23] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3814,11 +3766,9 @@ type Service struct { func (x *Service) Reset() { *x = Service{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[24] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Service) String() string { @@ -3829,7 +3779,7 @@ func (*Service) ProtoMessage() {} func (x *Service) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[24] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3876,11 +3826,9 @@ type LostEvent struct { func (x *LostEvent) Reset() { *x = LostEvent{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[25] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *LostEvent) String() string { @@ -3891,7 +3839,7 @@ func (*LostEvent) ProtoMessage() {} func (x *LostEvent) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[25] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3948,11 +3896,9 @@ type AgentEvent struct { func (x *AgentEvent) Reset() { *x = AgentEvent{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[26] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AgentEvent) String() string { @@ -3963,7 +3909,7 @@ func (*AgentEvent) ProtoMessage() {} func (x *AgentEvent) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[26] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4115,11 +4061,9 @@ type AgentEventUnknown struct { func (x *AgentEventUnknown) Reset() { *x = AgentEventUnknown{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[27] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AgentEventUnknown) String() string { @@ -4130,7 +4074,7 @@ func (*AgentEventUnknown) ProtoMessage() {} func (x *AgentEventUnknown) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[27] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4169,11 +4113,9 @@ type TimeNotification struct { func (x *TimeNotification) Reset() { *x = TimeNotification{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[28] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *TimeNotification) String() string { @@ -4184,7 +4126,7 @@ func (*TimeNotification) ProtoMessage() {} func (x *TimeNotification) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[28] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4218,11 +4160,9 @@ type PolicyUpdateNotification struct { func (x *PolicyUpdateNotification) Reset() { *x = PolicyUpdateNotification{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[29] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *PolicyUpdateNotification) String() string { @@ -4233,7 +4173,7 @@ func (*PolicyUpdateNotification) ProtoMessage() {} func (x *PolicyUpdateNotification) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[29] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4281,11 +4221,9 @@ type EndpointRegenNotification struct { func (x *EndpointRegenNotification) Reset() { *x = EndpointRegenNotification{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[30] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *EndpointRegenNotification) String() string { @@ -4296,7 +4234,7 @@ func (*EndpointRegenNotification) ProtoMessage() {} func (x *EndpointRegenNotification) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[30] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4346,11 +4284,9 @@ type EndpointUpdateNotification struct { func (x *EndpointUpdateNotification) Reset() { *x = EndpointUpdateNotification{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[31] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *EndpointUpdateNotification) String() string { @@ -4361,7 +4297,7 @@ func (*EndpointUpdateNotification) ProtoMessage() {} func (x *EndpointUpdateNotification) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[31] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4428,11 +4364,9 @@ type IPCacheNotification struct { func (x *IPCacheNotification) Reset() { *x = IPCacheNotification{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[32] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *IPCacheNotification) String() string { @@ -4443,7 +4377,7 @@ func (*IPCacheNotification) ProtoMessage() {} func (x *IPCacheNotification) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[32] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4525,11 +4459,9 @@ type ServiceUpsertNotificationAddr struct { func (x *ServiceUpsertNotificationAddr) Reset() { *x = ServiceUpsertNotificationAddr{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[33] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[33] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ServiceUpsertNotificationAddr) String() string { @@ -4540,7 +4472,7 @@ func (*ServiceUpsertNotificationAddr) ProtoMessage() {} func (x *ServiceUpsertNotificationAddr) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[33] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4588,11 +4520,9 @@ type ServiceUpsertNotification struct { func (x *ServiceUpsertNotification) Reset() { *x = ServiceUpsertNotification{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[34] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[34] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ServiceUpsertNotification) String() string { @@ -4603,7 +4533,7 @@ func (*ServiceUpsertNotification) ProtoMessage() {} func (x *ServiceUpsertNotification) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[34] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4692,11 +4622,9 @@ type ServiceDeleteNotification struct { func (x *ServiceDeleteNotification) Reset() { *x = ServiceDeleteNotification{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[35] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[35] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ServiceDeleteNotification) String() string { @@ -4707,7 +4635,7 @@ func (*ServiceDeleteNotification) ProtoMessage() {} func (x *ServiceDeleteNotification) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[35] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4740,11 +4668,9 @@ type NetworkInterface struct { func (x *NetworkInterface) Reset() { *x = NetworkInterface{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[36] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[36] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *NetworkInterface) String() string { @@ -4755,7 +4681,7 @@ func (*NetworkInterface) ProtoMessage() {} func (x *NetworkInterface) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[36] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4801,11 +4727,9 @@ type DebugEvent struct { func (x *DebugEvent) Reset() { *x = DebugEvent{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[37] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[37] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *DebugEvent) String() string { @@ -4816,7 +4740,7 @@ func (*DebugEvent) ProtoMessage() {} func (x *DebugEvent) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[37] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4908,11 +4832,9 @@ type FlowFilter_Experimental struct { func (x *FlowFilter_Experimental) Reset() { *x = FlowFilter_Experimental{} - if protoimpl.UnsafeEnabled { - mi := &file_flow_flow_proto_msgTypes[38] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_flow_flow_proto_msgTypes[38] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *FlowFilter_Experimental) String() string { @@ -4923,7 +4845,7 @@ func (*FlowFilter_Experimental) ProtoMessage() {} func (x *FlowFilter_Experimental) ProtoReflect() protoreflect.Message { mi := &file_flow_flow_proto_msgTypes[38] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -6017,476 +5939,6 @@ func file_flow_flow_proto_init() { if File_flow_flow_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_flow_flow_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*Flow); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[1].Exporter = func(v any, i int) any { - switch v := v.(*FileInfo); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[2].Exporter = func(v any, i int) any { - switch v := v.(*Layer4); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[3].Exporter = func(v any, i int) any { - switch v := v.(*Layer7); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[4].Exporter = func(v any, i int) any { - switch v := v.(*TraceContext); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[5].Exporter = func(v any, i int) any { - switch v := v.(*TraceParent); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[6].Exporter = func(v any, i int) any { - switch v := v.(*Endpoint); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[7].Exporter = func(v any, i int) any { - switch v := v.(*Workload); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[8].Exporter = func(v any, i int) any { - switch v := v.(*TCP); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[9].Exporter = func(v any, i int) any { - switch v := v.(*IP); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[10].Exporter = func(v any, i int) any { - switch v := v.(*Ethernet); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[11].Exporter = func(v any, i int) any { - switch v := v.(*TCPFlags); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[12].Exporter = func(v any, i int) any { - switch v := v.(*UDP); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[13].Exporter = func(v any, i int) any { - switch v := v.(*SCTP); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[14].Exporter = func(v any, i int) any { - switch v := v.(*ICMPv4); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[15].Exporter = func(v any, i int) any { - switch v := v.(*ICMPv6); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[16].Exporter = func(v any, i int) any { - switch v := v.(*Policy); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[17].Exporter = func(v any, i int) any { - switch v := v.(*EventTypeFilter); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[18].Exporter = func(v any, i int) any { - switch v := v.(*CiliumEventType); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[19].Exporter = func(v any, i int) any { - switch v := v.(*FlowFilter); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[20].Exporter = func(v any, i int) any { - switch v := v.(*DNS); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[21].Exporter = func(v any, i int) any { - switch v := v.(*HTTPHeader); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[22].Exporter = func(v any, i int) any { - switch v := v.(*HTTP); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[23].Exporter = func(v any, i int) any { - switch v := v.(*Kafka); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[24].Exporter = func(v any, i int) any { - switch v := v.(*Service); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[25].Exporter = func(v any, i int) any { - switch v := v.(*LostEvent); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[26].Exporter = func(v any, i int) any { - switch v := v.(*AgentEvent); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[27].Exporter = func(v any, i int) any { - switch v := v.(*AgentEventUnknown); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[28].Exporter = func(v any, i int) any { - switch v := v.(*TimeNotification); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[29].Exporter = func(v any, i int) any { - switch v := v.(*PolicyUpdateNotification); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[30].Exporter = func(v any, i int) any { - switch v := v.(*EndpointRegenNotification); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[31].Exporter = func(v any, i int) any { - switch v := v.(*EndpointUpdateNotification); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[32].Exporter = func(v any, i int) any { - switch v := v.(*IPCacheNotification); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[33].Exporter = func(v any, i int) any { - switch v := v.(*ServiceUpsertNotificationAddr); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[34].Exporter = func(v any, i int) any { - switch v := v.(*ServiceUpsertNotification); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[35].Exporter = func(v any, i int) any { - switch v := v.(*ServiceDeleteNotification); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[36].Exporter = func(v any, i int) any { - switch v := v.(*NetworkInterface); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[37].Exporter = func(v any, i int) any { - switch v := v.(*DebugEvent); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_flow_flow_proto_msgTypes[38].Exporter = func(v any, i int) any { - switch v := v.(*FlowFilter_Experimental); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } file_flow_flow_proto_msgTypes[2].OneofWrappers = []any{ (*Layer4_TCP)(nil), (*Layer4_UDP)(nil), diff --git a/vendor/github.com/cilium/cilium/api/v1/observer/observer.pb.go b/vendor/github.com/cilium/cilium/api/v1/observer/observer.pb.go index 09acc7a93e..cdcf09f39c 100644 --- a/vendor/github.com/cilium/cilium/api/v1/observer/observer.pb.go +++ b/vendor/github.com/cilium/cilium/api/v1/observer/observer.pb.go @@ -3,8 +3,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 -// protoc v5.28.2 +// protoc-gen-go v1.35.1 +// protoc v5.28.3 // source: observer/observer.proto package observer @@ -407,11 +407,9 @@ type ServerStatusRequest struct { func (x *ServerStatusRequest) Reset() { *x = ServerStatusRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_observer_observer_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_observer_observer_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ServerStatusRequest) String() string { @@ -422,7 +420,7 @@ func (*ServerStatusRequest) ProtoMessage() {} func (x *ServerStatusRequest) ProtoReflect() protoreflect.Message { mi := &file_observer_observer_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -474,11 +472,9 @@ type ServerStatusResponse struct { func (x *ServerStatusResponse) Reset() { *x = ServerStatusResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_observer_observer_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_observer_observer_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ServerStatusResponse) String() string { @@ -489,7 +485,7 @@ func (*ServerStatusResponse) ProtoMessage() {} func (x *ServerStatusResponse) ProtoReflect() protoreflect.Message { mi := &file_observer_observer_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -613,11 +609,9 @@ type GetFlowsRequest struct { func (x *GetFlowsRequest) Reset() { *x = GetFlowsRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_observer_observer_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_observer_observer_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *GetFlowsRequest) String() string { @@ -628,7 +622,7 @@ func (*GetFlowsRequest) ProtoMessage() {} func (x *GetFlowsRequest) ProtoReflect() protoreflect.Message { mi := &file_observer_observer_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -733,11 +727,9 @@ type GetFlowsResponse struct { func (x *GetFlowsResponse) Reset() { *x = GetFlowsResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_observer_observer_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_observer_observer_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *GetFlowsResponse) String() string { @@ -748,7 +740,7 @@ func (*GetFlowsResponse) ProtoMessage() {} func (x *GetFlowsResponse) ProtoReflect() protoreflect.Message { mi := &file_observer_observer_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -854,11 +846,9 @@ type GetAgentEventsRequest struct { func (x *GetAgentEventsRequest) Reset() { *x = GetAgentEventsRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_observer_observer_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_observer_observer_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *GetAgentEventsRequest) String() string { @@ -869,7 +859,7 @@ func (*GetAgentEventsRequest) ProtoMessage() {} func (x *GetAgentEventsRequest) ProtoReflect() protoreflect.Message { mi := &file_observer_observer_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -934,11 +924,9 @@ type GetAgentEventsResponse struct { func (x *GetAgentEventsResponse) Reset() { *x = GetAgentEventsResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_observer_observer_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_observer_observer_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *GetAgentEventsResponse) String() string { @@ -949,7 +937,7 @@ func (*GetAgentEventsResponse) ProtoMessage() {} func (x *GetAgentEventsResponse) ProtoReflect() protoreflect.Message { mi := &file_observer_observer_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1008,11 +996,9 @@ type GetDebugEventsRequest struct { func (x *GetDebugEventsRequest) Reset() { *x = GetDebugEventsRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_observer_observer_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_observer_observer_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *GetDebugEventsRequest) String() string { @@ -1023,7 +1009,7 @@ func (*GetDebugEventsRequest) ProtoMessage() {} func (x *GetDebugEventsRequest) ProtoReflect() protoreflect.Message { mi := &file_observer_observer_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1088,11 +1074,9 @@ type GetDebugEventsResponse struct { func (x *GetDebugEventsResponse) Reset() { *x = GetDebugEventsResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_observer_observer_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_observer_observer_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *GetDebugEventsResponse) String() string { @@ -1103,7 +1087,7 @@ func (*GetDebugEventsResponse) ProtoMessage() {} func (x *GetDebugEventsResponse) ProtoReflect() protoreflect.Message { mi := &file_observer_observer_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1147,11 +1131,9 @@ type GetNodesRequest struct { func (x *GetNodesRequest) Reset() { *x = GetNodesRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_observer_observer_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_observer_observer_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *GetNodesRequest) String() string { @@ -1162,7 +1144,7 @@ func (*GetNodesRequest) ProtoMessage() {} func (x *GetNodesRequest) ProtoReflect() protoreflect.Message { mi := &file_observer_observer_proto_msgTypes[8] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1189,11 +1171,9 @@ type GetNodesResponse struct { func (x *GetNodesResponse) Reset() { *x = GetNodesResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_observer_observer_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_observer_observer_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *GetNodesResponse) String() string { @@ -1204,7 +1184,7 @@ func (*GetNodesResponse) ProtoMessage() {} func (x *GetNodesResponse) ProtoReflect() protoreflect.Message { mi := &file_observer_observer_proto_msgTypes[9] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1254,11 +1234,9 @@ type Node struct { func (x *Node) Reset() { *x = Node{} - if protoimpl.UnsafeEnabled { - mi := &file_observer_observer_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_observer_observer_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Node) String() string { @@ -1269,7 +1247,7 @@ func (*Node) ProtoMessage() {} func (x *Node) ProtoReflect() protoreflect.Message { mi := &file_observer_observer_proto_msgTypes[10] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1362,11 +1340,9 @@ type TLS struct { func (x *TLS) Reset() { *x = TLS{} - if protoimpl.UnsafeEnabled { - mi := &file_observer_observer_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_observer_observer_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *TLS) String() string { @@ -1377,7 +1353,7 @@ func (*TLS) ProtoMessage() {} func (x *TLS) ProtoReflect() protoreflect.Message { mi := &file_observer_observer_proto_msgTypes[11] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1414,11 +1390,9 @@ type GetNamespacesRequest struct { func (x *GetNamespacesRequest) Reset() { *x = GetNamespacesRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_observer_observer_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_observer_observer_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *GetNamespacesRequest) String() string { @@ -1429,7 +1403,7 @@ func (*GetNamespacesRequest) ProtoMessage() {} func (x *GetNamespacesRequest) ProtoReflect() protoreflect.Message { mi := &file_observer_observer_proto_msgTypes[12] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1456,11 +1430,9 @@ type GetNamespacesResponse struct { func (x *GetNamespacesResponse) Reset() { *x = GetNamespacesResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_observer_observer_proto_msgTypes[13] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_observer_observer_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *GetNamespacesResponse) String() string { @@ -1471,7 +1443,7 @@ func (*GetNamespacesResponse) ProtoMessage() {} func (x *GetNamespacesResponse) ProtoReflect() protoreflect.Message { mi := &file_observer_observer_proto_msgTypes[13] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1504,11 +1476,9 @@ type Namespace struct { func (x *Namespace) Reset() { *x = Namespace{} - if protoimpl.UnsafeEnabled { - mi := &file_observer_observer_proto_msgTypes[14] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_observer_observer_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Namespace) String() string { @@ -1519,7 +1489,7 @@ func (*Namespace) ProtoMessage() {} func (x *Namespace) ProtoReflect() protoreflect.Message { mi := &file_observer_observer_proto_msgTypes[14] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1571,11 +1541,9 @@ type ExportEvent struct { func (x *ExportEvent) Reset() { *x = ExportEvent{} - if protoimpl.UnsafeEnabled { - mi := &file_observer_observer_proto_msgTypes[15] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_observer_observer_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ExportEvent) String() string { @@ -1586,7 +1554,7 @@ func (*ExportEvent) ProtoMessage() {} func (x *ExportEvent) ProtoReflect() protoreflect.Message { mi := &file_observer_observer_proto_msgTypes[15] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1717,11 +1685,9 @@ type GetFlowsRequest_Experimental struct { func (x *GetFlowsRequest_Experimental) Reset() { *x = GetFlowsRequest_Experimental{} - if protoimpl.UnsafeEnabled { - mi := &file_observer_observer_proto_msgTypes[16] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_observer_observer_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *GetFlowsRequest_Experimental) String() string { @@ -1732,7 +1698,7 @@ func (*GetFlowsRequest_Experimental) ProtoMessage() {} func (x *GetFlowsRequest_Experimental) ProtoReflect() protoreflect.Message { mi := &file_observer_observer_proto_msgTypes[16] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2089,212 +2055,6 @@ func file_observer_observer_proto_init() { if File_observer_observer_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_observer_observer_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*ServerStatusRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_observer_observer_proto_msgTypes[1].Exporter = func(v any, i int) any { - switch v := v.(*ServerStatusResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_observer_observer_proto_msgTypes[2].Exporter = func(v any, i int) any { - switch v := v.(*GetFlowsRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_observer_observer_proto_msgTypes[3].Exporter = func(v any, i int) any { - switch v := v.(*GetFlowsResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_observer_observer_proto_msgTypes[4].Exporter = func(v any, i int) any { - switch v := v.(*GetAgentEventsRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_observer_observer_proto_msgTypes[5].Exporter = func(v any, i int) any { - switch v := v.(*GetAgentEventsResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_observer_observer_proto_msgTypes[6].Exporter = func(v any, i int) any { - switch v := v.(*GetDebugEventsRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_observer_observer_proto_msgTypes[7].Exporter = func(v any, i int) any { - switch v := v.(*GetDebugEventsResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_observer_observer_proto_msgTypes[8].Exporter = func(v any, i int) any { - switch v := v.(*GetNodesRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_observer_observer_proto_msgTypes[9].Exporter = func(v any, i int) any { - switch v := v.(*GetNodesResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_observer_observer_proto_msgTypes[10].Exporter = func(v any, i int) any { - switch v := v.(*Node); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_observer_observer_proto_msgTypes[11].Exporter = func(v any, i int) any { - switch v := v.(*TLS); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_observer_observer_proto_msgTypes[12].Exporter = func(v any, i int) any { - switch v := v.(*GetNamespacesRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_observer_observer_proto_msgTypes[13].Exporter = func(v any, i int) any { - switch v := v.(*GetNamespacesResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_observer_observer_proto_msgTypes[14].Exporter = func(v any, i int) any { - switch v := v.(*Namespace); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_observer_observer_proto_msgTypes[15].Exporter = func(v any, i int) any { - switch v := v.(*ExportEvent); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_observer_observer_proto_msgTypes[16].Exporter = func(v any, i int) any { - switch v := v.(*GetFlowsRequest_Experimental); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } file_observer_observer_proto_msgTypes[3].OneofWrappers = []any{ (*GetFlowsResponse_Flow)(nil), (*GetFlowsResponse_NodeStatus)(nil), diff --git a/vendor/github.com/cilium/cilium/api/v1/observer/observer_grpc.pb.go b/vendor/github.com/cilium/cilium/api/v1/observer/observer_grpc.pb.go index a6838e8843..1cda804916 100644 --- a/vendor/github.com/cilium/cilium/api/v1/observer/observer_grpc.pb.go +++ b/vendor/github.com/cilium/cilium/api/v1/observer/observer_grpc.pb.go @@ -4,7 +4,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 -// - protoc v5.28.2 +// - protoc v5.28.3 // source: observer/observer.proto package observer diff --git a/vendor/github.com/cilium/cilium/api/v1/relay/relay.pb.go b/vendor/github.com/cilium/cilium/api/v1/relay/relay.pb.go index 3bd1ec8491..cba6fcf319 100644 --- a/vendor/github.com/cilium/cilium/api/v1/relay/relay.pb.go +++ b/vendor/github.com/cilium/cilium/api/v1/relay/relay.pb.go @@ -3,8 +3,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 -// protoc v5.28.2 +// protoc-gen-go v1.35.1 +// protoc v5.28.3 // source: relay/relay.proto package relay @@ -107,11 +107,9 @@ type NodeStatusEvent struct { func (x *NodeStatusEvent) Reset() { *x = NodeStatusEvent{} - if protoimpl.UnsafeEnabled { - mi := &file_relay_relay_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_relay_relay_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *NodeStatusEvent) String() string { @@ -122,7 +120,7 @@ func (*NodeStatusEvent) ProtoMessage() {} func (x *NodeStatusEvent) ProtoReflect() protoreflect.Message { mi := &file_relay_relay_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -215,20 +213,6 @@ func file_relay_relay_proto_init() { if File_relay_relay_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_relay_relay_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*NodeStatusEvent); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/vendor/github.com/cilium/cilium/cilium-cli/cli/clustermesh.go b/vendor/github.com/cilium/cilium/cilium-cli/cli/clustermesh.go index 4677c1e938..adf3cf08a2 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/cli/clustermesh.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/cli/clustermesh.go @@ -337,4 +337,5 @@ func addCommonConnectFlags(cmd *cobra.Command, params *clustermesh.Parameters) { cmd.Flags().StringSliceVar(¶ms.DestinationContext, "destination-context", []string{}, "Comma separated list of Kubernetes configuration contexts of destination cluster") cmd.Flags().StringSliceVar(¶ms.DestinationEndpoints, "destination-endpoint", []string{}, "IP of ClusterMesh service of destination cluster") cmd.Flags().StringSliceVar(¶ms.SourceEndpoints, "source-endpoint", []string{}, "IP of ClusterMesh service of source cluster") + cmd.Flags().IntVar(¶ms.Parallel, "parallel", 1, "Number of parallel connection of destination cluster") } diff --git a/vendor/github.com/cilium/cilium/cilium-cli/cli/cmd.go b/vendor/github.com/cilium/cilium/cilium-cli/cli/cmd.go index 4159f3cd28..592c64de81 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/cli/cmd.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/cli/cmd.go @@ -18,6 +18,7 @@ var ( contextName string namespace string helmReleaseName string + kubeConfig string k8sClient *k8s.Client ) @@ -44,7 +45,7 @@ func NewCiliumCommand(hooks api.Hooks) *cobra.Command { } } - c, err := k8s.NewClient(contextName, "", namespace) + c, err := k8s.NewClient(contextName, kubeConfig, namespace) if err != nil { return fmt.Errorf("unable to create Kubernetes client: %w", err) } @@ -84,6 +85,7 @@ cilium connectivity test`, cmd.PersistentFlags().StringVar(&contextName, "context", "", "Kubernetes configuration context") cmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", "kube-system", "Namespace Cilium is running in") cmd.PersistentFlags().StringVar(&helmReleaseName, "helm-release-name", "cilium", "Helm release name") + cmd.PersistentFlags().StringVar(&kubeConfig, "kubeconfig", "", "Path to the kubeconfig file") cmd.AddCommand( newCmdBgp(), diff --git a/vendor/github.com/cilium/cilium/cilium-cli/cli/connectivity.go b/vendor/github.com/cilium/cilium/cilium-cli/cli/connectivity.go index 9565b07bfc..a7214c9009 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/cli/connectivity.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/cli/connectivity.go @@ -152,6 +152,7 @@ func newCmdConnectivityTest(hooks api.Hooks) *cobra.Command { cmd.Flags().StringVar(¶ms.DNSTestServerImage, "dns-test-server-image", defaults.ConnectivityDNSTestServerImage, "Image path to use for CoreDNS") cmd.Flags().StringVar(¶ms.TestConnDisruptImage, "test-conn-disrupt-image", defaults.ConnectivityTestConnDisruptImage, "Image path to use for connection disruption tests") cmd.Flags().StringVar(¶ms.FRRImage, "frr-image", defaults.ConnectivityTestFRRImage, "Image path to use for FRR") + cmd.Flags().StringVar(¶ms.SocatImage, "socat-image", defaults.ConnectivityTestSocatImage, "Image path to use for multicast tests") cmd.Flags().UintVar(¶ms.Retry, "retry", defaults.ConnectRetry, "Number of retries on connection failure to external targets") cmd.Flags().DurationVar(¶ms.RetryDelay, "retry-delay", defaults.ConnectRetryDelay, "Delay between retries for external targets") diff --git a/vendor/github.com/cilium/cilium/cilium-cli/cli/hubble.go b/vendor/github.com/cilium/cilium/cilium-cli/cli/hubble.go index a22d835832..3d8d6333e2 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/cli/hubble.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/cli/hubble.go @@ -6,6 +6,7 @@ package cli import ( "context" "os" + "os/signal" "github.com/spf13/cobra" @@ -37,11 +38,11 @@ func newCmdPortForwardCommand() *cobra.Command { Use: "port-forward", Short: "Forward the relay port to the local machine", Long: ``, - RunE: func(_ *cobra.Command, _ []string) error { - params.Context = contextName - params.Namespace = namespace - ctx := context.Background() + RunE: func(cmd *cobra.Command, _ []string) error { + ctx, cancel := signal.NotifyContext(cmd.Context(), os.Interrupt, os.Kill) + defer cancel() + params.Namespace = namespace if err := params.RelayPortForwardCommand(ctx, k8sClient); err != nil { fatalf("Unable to port forward: %s", err) } @@ -49,7 +50,7 @@ func newCmdPortForwardCommand() *cobra.Command { }, } - cmd.Flags().IntVar(¶ms.PortForward, "port-forward", 4245, "Local port to forward to") + cmd.Flags().IntVar(¶ms.PortForward, "port-forward", 4245, "Local port to forward to. 0 will select a random port.") return cmd } @@ -62,18 +63,19 @@ func newCmdUI() *cobra.Command { cmd := &cobra.Command{ Use: "ui", Short: "Open the Hubble UI", - RunE: func(_ *cobra.Command, _ []string) error { - params.Context = contextName - params.Namespace = namespace + RunE: func(cmd *cobra.Command, _ []string) error { + ctx, cancel := signal.NotifyContext(cmd.Context(), os.Interrupt, os.Kill) + defer cancel() - if err := params.UIPortForwardCommand(); err != nil { + params.Namespace = namespace + if err := params.UIPortForwardCommand(ctx, k8sClient); err != nil { fatalf("Unable to port forward: %s", err) } return nil }, } - cmd.Flags().IntVar(¶ms.UIPortForward, "port-forward", 12000, "Local port to use for the port forward") + cmd.Flags().IntVar(¶ms.UIPortForward, "port-forward", 12000, "Local port to forward to. 0 will select a random port.") cmd.Flags().BoolVar(¶ms.UIOpenBrowser, "open-browser", true, "When --open-browser=false is supplied, cilium Hubble UI will not open the browser") return cmd diff --git a/vendor/github.com/cilium/cilium/cilium-cli/cli/install.go b/vendor/github.com/cilium/cilium/cilium-cli/cli/install.go index 0c0f73c27e..3e53ab095b 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/cli/install.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/cli/install.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "os" + "time" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -17,7 +18,6 @@ import ( "github.com/cilium/cilium/cilium-cli/defaults" "github.com/cilium/cilium/cilium-cli/hubble" "github.com/cilium/cilium/cilium-cli/install" - "github.com/cilium/cilium/pkg/inctimer" ) // addCommonInstallFlags adds install command flags that are shared between install and upgrade commands. @@ -137,7 +137,7 @@ func newCmdUninstallWithHelm() *cobra.Command { break } select { - case <-inctimer.After(defaults.WaitRetryInterval): + case <-time.After(defaults.WaitRetryInterval): case <-ctx.Done(): fatalf("Timed out waiting for Hubble Pods to terminate") } diff --git a/vendor/github.com/cilium/cilium/cilium-cli/clustermesh/clustermesh.go b/vendor/github.com/cilium/cilium/cilium-cli/clustermesh/clustermesh.go index 2ff1798373..37a71be38f 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/clustermesh/clustermesh.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/clustermesh/clustermesh.go @@ -20,6 +20,7 @@ import ( "slices" "strconv" "strings" + "sync" "text/tabwriter" "time" @@ -37,6 +38,7 @@ import ( "github.com/cilium/cilium/cilium-cli/status" "github.com/cilium/cilium/cilium-cli/utils/wait" ciliumv2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" + "github.com/cilium/cilium/pkg/lock" ) const ( @@ -78,7 +80,7 @@ type k8sClusterMeshImplementation interface { CreateCiliumExternalWorkload(ctx context.Context, cew *ciliumv2.CiliumExternalWorkload, opts metav1.CreateOptions) (*ciliumv2.CiliumExternalWorkload, error) DeleteCiliumExternalWorkload(ctx context.Context, name string, opts metav1.DeleteOptions) error ListCiliumEndpoints(ctx context.Context, namespace string, options metav1.ListOptions) (*ciliumv2.CiliumEndpointList, error) - CiliumLogs(ctx context.Context, namespace, pod string, since time.Time) (string, error) + CiliumLogs(ctx context.Context, namespace, pod string, since time.Time, previous bool) (string, error) } type K8sClusterMesh struct { @@ -100,6 +102,7 @@ type Parameters struct { WaitDuration time.Duration DestinationEndpoints []string SourceEndpoints []string + Parallel int Writer io.Writer Labels map[string]string IPv4AllocCIDR string @@ -1823,13 +1826,35 @@ func (k *K8sClusterMesh) connectRemoteWithHelm(ctx context.Context, localCluster cn = append(state.remoteClusterNames, localClusterName) } + maxGoroutines := k.params.Parallel + + sem := make(chan struct{}, maxGoroutines) + var wg sync.WaitGroup + var mu lock.Mutex + var firstErr error + for aiClusterName, remoteClient := range rc { - err := k.connectSingleRemoteWithHelm(ctx, remoteClient, cn, helmValues[aiClusterName]) - if err != nil { - return err - } + wg.Add(1) + + sem <- struct{}{} + + go func(cn []string, rc *k8s.Client, helmVals map[string]interface{}) { + defer wg.Done() + defer func() { <-sem }() + + if err := k.connectSingleRemoteWithHelm(ctx, rc, cn, helmVals); err != nil { + mu.Lock() + if firstErr == nil { + firstErr = err + } + mu.Unlock() + } + }(cn, remoteClient, helmValues[aiClusterName]) } - return nil + + wg.Wait() + + return firstErr } func (k *K8sClusterMesh) connectSingleRemoteWithHelm(ctx context.Context, remoteClient *k8s.Client, clusterNames []string, helmValues map[string]interface{}) error { diff --git a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/builder.go b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/builder.go index ce55bfef20..685159ec05 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/builder.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/builder.go @@ -54,6 +54,9 @@ var ( //go:embed manifests/client-egress-to-cidr-external-deny.yaml clientEgressToCIDRExternalDenyPolicyYAML string + //go:embed manifests/client-egress-to-cidrgroup-external-deny.yaml + clientEgressToCIDRGroupExternalDenyPolicyYAML string + //go:embed manifests/client-egress-l7-http.yaml clientEgressL7HTTPPolicyYAML string @@ -210,6 +213,7 @@ func concurrentTests(connTests []*check.ConnectivityTest) error { clientWithServiceAccountEgressToEchoDeny{}, clientEgressToEchoServiceAccountDeny{}, clientEgressToCidrDeny{}, + clientEgressToCidrgroupDeny{}, clientEgressToCidrDenyDefault{}, clusterMeshEndpointSliceSync{}, health{}, @@ -250,6 +254,8 @@ func concurrentTests(connTests []*check.ConnectivityTest) error { localRedirectPolicyWithNodeDNS{}, noFragmentation{}, bgpControlPlane{}, + multicast{}, + strictModeEncryption{}, } return injectTests(tests, connTests...) } @@ -274,6 +280,7 @@ func renderTemplates(param check.Parameters) (map[string]string, error) { "clientEgressToCIDRExternalPolicyKNPYAML": clientEgressToCIDRExternalPolicyKNPYAML, "clientEgressToCIDRNodeKNPYAML": clientEgressToCIDRNodeKNPYAML, "clientEgressToCIDRExternalDenyPolicyYAML": clientEgressToCIDRExternalDenyPolicyYAML, + "clientEgressToCIDRGroupExternalDenyPolicyYAML": clientEgressToCIDRGroupExternalDenyPolicyYAML, "clientEgressL7HTTPPolicyYAML": clientEgressL7HTTPPolicyYAML, "clientEgressL7HTTPPolicyPortRangeYAML": clientEgressL7HTTPPolicyPortRangeYAML, "clientEgressL7HTTPNamedPortPolicyYAML": clientEgressL7HTTPNamedPortPolicyYAML, diff --git a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/client_egress_to_cidrgroup_deny.go b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/client_egress_to_cidrgroup_deny.go new file mode 100644 index 0000000000..bd89f2b5c7 --- /dev/null +++ b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/client_egress_to_cidrgroup_deny.go @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Cilium + +package builder + +import ( + "github.com/cilium/cilium/cilium-cli/connectivity/check" + "github.com/cilium/cilium/cilium-cli/connectivity/tests" + "github.com/cilium/cilium/cilium-cli/utils/features" +) + +type clientEgressToCidrgroupDeny struct{} + +func (t clientEgressToCidrgroupDeny) build(ct *check.ConnectivityTest, templates map[string]string) { + // This policy denies L3 traffic to ExternalCIDR except ExternalIP/32 + // It does so using a CiliumCIDRGroup + newTest("client-egress-to-cidrgroup-deny", ct). + WithCiliumPolicy(allowAllEgressPolicyYAML). // Allow all egress traffic + WithCiliumPolicy(templates["clientEgressToCIDRGroupExternalDenyPolicyYAML"]). + WithScenarios( + tests.PodToCIDR(tests.WithRetryDestIP(ct.Params().ExternalIP)), // Denies all traffic to ExternalOtherIP, but allow ExternalIP + ). + WithExpectations(func(a *check.Action) (egress, ingress check.Result) { + if a.Destination().Address(features.GetIPFamily(ct.Params().ExternalOtherIP)) == ct.Params().ExternalOtherIP { + return check.ResultPolicyDenyEgressDrop, check.ResultNone + } + if a.Destination().Address(features.GetIPFamily(ct.Params().ExternalIP)) == ct.Params().ExternalIP { + return check.ResultOK, check.ResultNone + } + return check.ResultDrop, check.ResultDrop + }) +} diff --git a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/egress_gateway.go b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/egress_gateway.go index d505dea9f6..3925cd4c58 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/egress_gateway.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/egress_gateway.go @@ -12,7 +12,8 @@ import ( type egressGateway struct{} func (t egressGateway) build(ct *check.ConnectivityTest, _ map[string]string) { - newTest("egress-gateway", ct). + // Prefix the test name with `seq-` to run it sequentially. + newTest("seq-egress-gateway", ct). WithCondition(func() bool { return ct.Params().IncludeUnsafeTests }). WithCiliumEgressGatewayPolicy(check.CiliumEgressGatewayPolicyParams{ Name: "cegp-sample-client", diff --git a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/egress_gateway_with_l7_policy.go b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/egress_gateway_with_l7_policy.go index 8a63e048b1..79433fb31b 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/egress_gateway_with_l7_policy.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/egress_gateway_with_l7_policy.go @@ -21,7 +21,8 @@ var clientEgressL7HTTPExternalYAML string type egressGatewayWithL7Policy struct{} func (t egressGatewayWithL7Policy) build(ct *check.ConnectivityTest, templates map[string]string) { - newTest("egress-gateway-with-l7-policy", ct). + // Prefix the test name with `seq-` to run it sequentially. + newTest("seq-egress-gateway-with-l7-policy", ct). WithCondition(func() bool { return versioncheck.MustCompile(">=1.16.0")(ct.CiliumVersion) && ct.Params().IncludeUnsafeTests }). diff --git a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/from_cidr_host_netns.go b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/from_cidr_host_netns.go index f865469f6d..429e37d3e1 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/from_cidr_host_netns.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/from_cidr_host_netns.go @@ -12,7 +12,8 @@ import ( type fromCidrHostNetns struct{} func (t fromCidrHostNetns) build(ct *check.ConnectivityTest, templates map[string]string) { - newTest("from-cidr-host-netns", ct). + // Prefix the test name with `seq-` to run it sequentially. + newTest("seq-from-cidr-host-netns", ct). WithCondition(func() bool { return ct.Params().IncludeUnsafeTests }). WithFeatureRequirements(features.RequireEnabled(features.NodeWithoutCilium)). WithCiliumPolicy(templates["echoIngressFromCIDRYAML"]). diff --git a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/manifests/client-egress-to-cidrgroup-external-deny.yaml b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/manifests/client-egress-to-cidrgroup-external-deny.yaml new file mode 100644 index 0000000000..361589271a --- /dev/null +++ b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/manifests/client-egress-to-cidrgroup-external-deny.yaml @@ -0,0 +1,27 @@ +# This policy denies packets towards {{.ExternalOtherIP}}, but not {{.ExternalIP}} +# Please note that if there is no other allowed rule, the policy +# will be automatically denied {{.ExternalIP}} as well. + +apiVersion: "cilium.io/v2alpha1" +kind: CiliumCIDRGroup +metadata: + name: cilium-test-external-cidr +spec: + externalCIDRs: + - "{{.ExternalCIDR}}" + +--- + +apiVersion: "cilium.io/v2" +kind: CiliumNetworkPolicy +metadata: + name: client-egress-to-cidrgroup-deny +spec: + endpointSelector: + matchLabels: + kind: client + egressDeny: + - toCIDRSet: + - cidrGroupRef: cilium-test-external-cidr + except: + - "{{.ExternalIP}}/32" diff --git a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/multicast.go b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/multicast.go new file mode 100644 index 0000000000..6753a3ccf3 --- /dev/null +++ b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/multicast.go @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Cilium + +package builder + +import ( + "github.com/cilium/cilium/cilium-cli/connectivity/check" + "github.com/cilium/cilium/cilium-cli/connectivity/tests" + "github.com/cilium/cilium/cilium-cli/utils/features" + + "github.com/cilium/cilium/pkg/versioncheck" +) + +type multicast struct{} + +func (t multicast) build(ct *check.ConnectivityTest, _ map[string]string) { + newTest("multicast", ct). + WithCondition(func() bool { + return versioncheck.MustCompile(">=1.16.0")(ct.CiliumVersion) + }). + WithCondition(func() bool { + return ct.Params().IncludeUnsafeTests + }). + WithFeatureRequirements( + features.RequireEnabled(features.Multicast), + ). + WithScenarios(tests.SocatMulticast()) +} diff --git a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/pod_to_pod_encryption.go b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/pod_to_pod_encryption.go index 2231a12f13..33fa5fedaa 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/pod_to_pod_encryption.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/pod_to_pod_encryption.go @@ -29,13 +29,11 @@ func (t podToPodEncryption) build(ct *check.ConnectivityTest, _ map[string]strin WithCondition(func() bool { return !ct.Params().SingleNode }). WithFeatureRequirements( features.RequireEnabled(features.L7Proxy), - // Once https://github.com/cilium/cilium/issues/33168 is fixed, we - // can enable for IPsec too. - features.RequireMode(features.EncryptionPod, "wireguard"), + features.RequireEnabled(features.EncryptionPod), ). WithCiliumPolicy(clientsEgressL7HTTPFromAnyPolicyYAML). WithCiliumPolicy(echoIngressL7HTTPFromAnywherePolicyYAML). WithScenarios( - tests.PodToPodEncryption(features.RequireEnabled(features.EncryptionPod)), + tests.PodToPodEncryption(), ) } diff --git a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/strict_mode_encryption.go b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/strict_mode_encryption.go new file mode 100644 index 0000000000..4a06db3b3e --- /dev/null +++ b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/builder/strict_mode_encryption.go @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Cilium + +package builder + +import ( + "github.com/cilium/cilium/cilium-cli/connectivity/check" + "github.com/cilium/cilium/cilium-cli/connectivity/tests" + "github.com/cilium/cilium/cilium-cli/utils/features" +) + +type strictModeEncryption struct{} + +func (t strictModeEncryption) build(ct *check.ConnectivityTest, _ map[string]string) { + newTest("strict-mode-encryption", ct). + WithCondition(func() bool { return ct.Params().IncludeUnsafeTests }). + // Until https://github.com/cilium/cilium/pull/35454 is backported to <1.17.0 + WithCiliumVersion(">=1.17.0"). + WithFeatureRequirements( + features.RequireEnabled(features.EncryptionStrictMode), + // Strict mode is only supported with WireGuard + features.RequireMode(features.EncryptionPod, "wireguard"), + // Strict mode always allows host-to-host tunnel traffic + features.RequireDisabled(features.Tunnel), + ). + WithScenarios(tests.PodToPodMissingIPCache()). + WithExpectations(func(_ *check.Action) (egress, ingress check.Result) { + return check.ResultEgressUnencryptedDrop, check.ResultEgressUnencryptedDrop + }) +} diff --git a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/action.go b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/action.go index c1da052b74..ef12bce90d 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/action.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/action.go @@ -28,7 +28,6 @@ import ( "github.com/cilium/cilium/cilium-cli/defaults" "github.com/cilium/cilium/cilium-cli/utils/features" hubprinter "github.com/cilium/cilium/hubble/pkg/printer" - "github.com/cilium/cilium/pkg/inctimer" "github.com/cilium/cilium/pkg/lock" ) @@ -802,7 +801,7 @@ func (a *Action) waitForRelay(ctx context.Context, client observer.ObserverClien select { case <-ctx.Done(): return fmt.Errorf("hubble server status failure: %w", ctx.Err()) - case <-inctimer.After(time.Second): + case <-time.After(time.Second): a.Debug("retrying hubble relay server status request") } } diff --git a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/check.go b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/check.go index 28de1d9d4d..8ad92d0b03 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/check.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/check.go @@ -63,6 +63,7 @@ type Parameters struct { JSONMockImage string TestConnDisruptImage string FRRImage string + SocatImage string AgentDaemonSetName string DNSTestServerImage string IncludeUnsafeTests bool diff --git a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/context.go b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/context.go index 35e3d0de7f..c57892d1f3 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/context.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/context.go @@ -31,6 +31,10 @@ import ( "github.com/cilium/cilium/pkg/lock" ) +const ( + socatMulticastTestMsg = "Multicast test message" +) + // ConnectivityTest is the root context of the connectivity test suite // and holds all resources belonging to it. It implements interface // ConnectivityTest and is instantiated once at the start of the program, @@ -71,6 +75,8 @@ type ConnectivityTest struct { lrpClientPods map[string]Pod lrpBackendPods map[string]Pod frrPods []Pod + socatServerPods []Pod + socatClientPods []Pod hostNetNSPodsByNode map[string]Pod secondaryNetworkNodeIPv4 map[string]string // node name => secondary ip @@ -211,6 +217,8 @@ func NewConnectivityTest( clientCPPods: make(map[string]Pod), lrpClientPods: make(map[string]Pod), lrpBackendPods: make(map[string]Pod), + socatServerPods: []Pod{}, + socatClientPods: []Pod{}, perfClientPods: []Pod{}, perfServerPod: []Pod{}, PerfResults: []common.PerfSummary{}, @@ -1076,6 +1084,14 @@ func (ct *ConnectivityTest) PerfClientPods() []Pod { return ct.perfClientPods } +func (ct *ConnectivityTest) SocatServerPods() []Pod { + return ct.socatServerPods +} + +func (ct *ConnectivityTest) SocatClientPods() []Pod { + return ct.socatClientPods +} + func (ct *ConnectivityTest) EchoPods() map[string]Pod { return ct.echoPods } @@ -1210,3 +1226,26 @@ func (ct *ConnectivityTest) EchoServicePrefixes(ipFamily features.IPFamily) []ne } return res } + +// Multicast packet sender +// This command exits with exit code 0 +// WITHOUT waiting for a second after receiving a packet. +func (ct *ConnectivityTest) SocatServer1secCommand(peer TestPeer, port int, group string) []string { + addr := peer.Address(features.IPFamilyV4) + cmdStr := fmt.Sprintf("timeout 5 socat STDIO UDP4-RECVFROM:%d,ip-add-membership=%s:%s", port, group, addr) + cmd := strings.Fields(cmdStr) + return cmd +} + +// Multicast packet receiver +func (ct *ConnectivityTest) SocatClientCommand(port int, group string) []string { + portStr := fmt.Sprintf("%d", port) + cmdStr := fmt.Sprintf(`for i in $(seq 1 10000); do echo "%s" | socat - UDP-DATAGRAM:%s:%s; sleep 0.1; done`, socatMulticastTestMsg, group, portStr) + cmd := []string{"/bin/sh", "-c", cmdStr} + return cmd +} + +func (ct *ConnectivityTest) KillMulticastTestSender() []string { + cmd := []string{"pkill", "-f", socatMulticastTestMsg} + return cmd +} diff --git a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/deployment.go b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/deployment.go index 8ada6a1ed9..70a1828f71 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/deployment.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/deployment.go @@ -395,6 +395,30 @@ func (ct *ConnectivityTest) ingresses() map[string]string { return ingresses } +// maybeNodeToNodeEncryptionAffinity returns a node affinity term to prefer nodes +// not being part of the control plane when node to node encryption is enabled, +// because they are excluded by default from node to node encryption. This logic +// is currently suboptimal as it only accounts for the default selector, for the +// sake of simplicity, but it should cover all common use cases. +func (ct *ConnectivityTest) maybeNodeToNodeEncryptionAffinity() *corev1.NodeAffinity { + encryptNode, _ := ct.Feature(features.EncryptionNode) + if !encryptNode.Enabled || encryptNode.Mode == "" { + return nil + } + + return &corev1.NodeAffinity{ + PreferredDuringSchedulingIgnoredDuringExecution: []corev1.PreferredSchedulingTerm{{ + Weight: 100, + Preference: corev1.NodeSelectorTerm{ + MatchExpressions: []corev1.NodeSelectorRequirement{{ + Key: "node-role.kubernetes.io/control-plane", + Operator: corev1.NodeSelectorOpDoesNotExist, + }}, + }, + }}, + } +} + // deploy ensures the test Namespace, Services and Deployments are running on the cluster. func (ct *ConnectivityTest) deploy(ctx context.Context) error { var err error @@ -633,6 +657,7 @@ func (ct *ConnectivityTest) deploy(ctx context.Context) error { }, }, }, + NodeAffinity: ct.maybeNodeToNodeEncryptionAffinity(), }, ReadinessProbe: newLocalReadinessProbe(containerPort, "/"), }, ct.params.DNSTestServerImage) @@ -655,6 +680,7 @@ func (ct *ConnectivityTest) deploy(ctx context.Context) error { Image: ct.params.CurlImage, Command: []string{"/usr/bin/pause"}, Annotations: ct.params.DeploymentAnnotations.Match(clientDeploymentName), + Affinity: &corev1.Affinity{NodeAffinity: ct.maybeNodeToNodeEncryptionAffinity()}, NodeSelector: ct.params.NodeSelector, }) _, err = ct.clients.src.CreateServiceAccount(ctx, ct.params.TestNamespace, k8s.NewServiceAccount(clientDeploymentName), metav1.CreateOptions{}) @@ -691,6 +717,7 @@ func (ct *ConnectivityTest) deploy(ctx context.Context) error { }, }, }, + NodeAffinity: ct.maybeNodeToNodeEncryptionAffinity(), }, NodeSelector: ct.params.NodeSelector, }) @@ -729,6 +756,7 @@ func (ct *ConnectivityTest) deploy(ctx context.Context) error { }, }, }, + NodeAffinity: ct.maybeNodeToNodeEncryptionAffinity(), }, NodeSelector: ct.params.NodeSelector, }) @@ -832,6 +860,7 @@ func (ct *ConnectivityTest) deploy(ctx context.Context) error { }, }, }, + NodeAffinity: ct.maybeNodeToNodeEncryptionAffinity(), }, NodeSelector: ct.params.NodeSelector, ReadinessProbe: newLocalReadinessProbe(containerPort, "/"), @@ -1042,6 +1071,34 @@ func (ct *ConnectivityTest) deploy(ctx context.Context) error { } } + if ct.Features[features.Multicast].Enabled { + _, err = ct.clients.src.GetDeployment(ctx, ct.params.TestNamespace, socatClientDeploymentName, metav1.GetOptions{}) + if err != nil { + ct.Logf("✨ [%s] Deploying %s deployment...", ct.clients.src.ClusterName(), socatClientDeploymentName) + ds := NewSocatClientDeployment(ct.params) + _, err = ct.clients.src.CreateServiceAccount(ctx, ct.params.TestNamespace, k8s.NewServiceAccount(socatClientDeploymentName), metav1.CreateOptions{}) + if err != nil { + return fmt.Errorf("unable to create service account %s: %w", socatClientDeploymentName, err) + } + _, err = ct.clients.src.CreateDeployment(ctx, ct.params.TestNamespace, ds, metav1.CreateOptions{}) + if err != nil { + return fmt.Errorf("unable to create deployment %s: %w", socatClientDeploymentName, err) + } + } + } + + if ct.Features[features.Multicast].Enabled { + _, err = ct.clients.src.GetDaemonSet(ctx, ct.params.TestNamespace, socatServerDaemonsetName, metav1.GetOptions{}) + if err != nil { + ct.Logf("✨ [%s] Deploying %s daemonset...", ct.clients.src.ClusterName(), socatServerDaemonsetName) + ds := NewSocatServerDaemonSet(ct.params) + _, err = ct.clients.src.CreateDaemonSet(ctx, ct.params.TestNamespace, ds, metav1.CreateOptions{}) + if err != nil { + return fmt.Errorf("unable to create daemonset %s: %w", socatServerDaemonsetName, err) + } + } + } + return nil } @@ -1202,6 +1259,10 @@ func (ct *ConnectivityTest) deploymentList() (srcList []string, dstList []string srcList = append(srcList, lrpBackendDeploymentName) } + if ct.Features[features.Multicast].Enabled { + srcList = append(srcList, socatClientDeploymentName) + } + return srcList, dstList } @@ -1212,6 +1273,8 @@ func (ct *ConnectivityTest) deleteDeployments(ctx context.Context, client *k8s.C _ = client.DeleteDeployment(ctx, ct.params.TestNamespace, clientDeploymentName, metav1.DeleteOptions{}) _ = client.DeleteDeployment(ctx, ct.params.TestNamespace, client2DeploymentName, metav1.DeleteOptions{}) _ = client.DeleteDeployment(ctx, ct.params.TestNamespace, client3DeploymentName, metav1.DeleteOptions{}) + _ = client.DeleteDeployment(ctx, ct.params.TestNamespace, socatClientDeploymentName, metav1.DeleteOptions{}) + _ = client.DeleteDeployment(ctx, ct.params.TestNamespace, socatServerDaemonsetName, metav1.DeleteOptions{}) // Q:Daemonset in here is OK? _ = client.DeleteServiceAccount(ctx, ct.params.TestNamespace, echoSameNodeDeploymentName, metav1.DeleteOptions{}) _ = client.DeleteServiceAccount(ctx, ct.params.TestNamespace, echoOtherNodeDeploymentName, metav1.DeleteOptions{}) _ = client.DeleteServiceAccount(ctx, ct.params.TestNamespace, clientDeploymentName, metav1.DeleteOptions{}) @@ -1419,6 +1482,35 @@ func (ct *ConnectivityTest) validateDeployment(ctx context.Context) error { } } + if ct.Features[features.Multicast].Enabled { + // socat client pods + socatCilentPods, err := ct.clients.src.ListPods(ctx, ct.params.TestNamespace, metav1.ListOptions{LabelSelector: "name=" + socatClientDeploymentName}) + if err != nil { + return fmt.Errorf("unable to list socat client pods: %w", err) + } + for _, pod := range socatCilentPods.Items { + ct.socatClientPods = append(ct.socatClientPods, Pod{ + K8sClient: ct.client, + Pod: pod.DeepCopy(), + }) + } + + // socat server pods + if err := WaitForDaemonSet(ctx, ct, ct.clients.src, ct.Params().TestNamespace, socatServerDaemonsetName); err != nil { + return err + } + socatServerPods, err := ct.clients.src.ListPods(ctx, ct.params.TestNamespace, metav1.ListOptions{LabelSelector: "name=" + socatServerDaemonsetName}) + if err != nil { + return fmt.Errorf("unable to list socat server pods: %w", err) + } + for _, pod := range socatServerPods.Items { + ct.socatServerPods = append(ct.socatServerPods, Pod{ + K8sClient: ct.client, + Pod: pod.DeepCopy(), + }) + } + } + for _, cp := range ct.clientPods { if err := WaitForCoreDNS(ctx, ct, cp); err != nil { return err diff --git a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/features.go b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/features.go index 30063d0c2e..a5ed68c62c 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/features.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/features.go @@ -66,6 +66,7 @@ func (ct *ConnectivityTest) extractFeaturesFromRuntimeConfig(ctx context.Context result[features.EncryptionNode] = features.Status{ Enabled: cfg.EncryptNode, + Mode: cfg.NodeEncryptionOptOutLabelsString, } isFeatureKNPEnabled, err := ct.isFeatureKNPEnabled(cfg.EnableK8sNetworkPolicy) diff --git a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/metrics.go b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/metrics.go index 6ef5089681..89d677ba3f 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/metrics.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/metrics.go @@ -12,7 +12,7 @@ import ( dto "github.com/prometheus/client_model/go" "github.com/prometheus/common/expfmt" - "github.com/cilium/cilium/cilium-cli/k8s" + "github.com/cilium/cilium/pkg/k8s" ) // metricsURLFormat is the path format to retrieve the metrics on the diff --git a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/netshoot.go b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/netshoot.go new file mode 100644 index 0000000000..60360b7e1b --- /dev/null +++ b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/netshoot.go @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Cilium + +package check + +import appsv1 "k8s.io/api/apps/v1" + +const ( + // SocatServerPort is the port on which the socat server listens. + socatServerDaemonsetName = "socat-server-daemonset" + socatClientDeploymentName = "socat-client" +) + +func NewSocatServerDaemonSet(params Parameters) *appsv1.DaemonSet { + ds := newDaemonSet(daemonSetParameters{ + Name: socatServerDaemonsetName, + Kind: socatServerDaemonsetName, + Image: params.SocatImage, + Command: []string{"/bin/sh", "-c", "sleep 10000000"}, + }) + return ds +} + +func NewSocatClientDeployment(params Parameters) *appsv1.Deployment { + dep := newDeployment(deploymentParameters{ + Name: socatClientDeploymentName, + Kind: socatClientDeploymentName, + Image: params.SocatImage, + Replicas: 1, + Command: []string{"/bin/sh", "-c", "sleep 10000000"}, + }) + return dep +} diff --git a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/policy.go b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/policy.go index f8d9ae1483..c78ee971a5 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/policy.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/policy.go @@ -5,9 +5,8 @@ package check import ( "context" + "encoding/json" "fmt" - "maps" - "reflect" "strconv" "strings" "time" @@ -15,16 +14,20 @@ import ( networkingv1 "k8s.io/api/networking/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" - clientsetscheme "k8s.io/client-go/kubernetes/scheme" flowpb "github.com/cilium/cilium/api/v1/flow" "github.com/cilium/cilium/cilium-cli/defaults" "github.com/cilium/cilium/cilium-cli/k8s" + "github.com/cilium/cilium/cilium-cli/utils/features" + k8sConst "github.com/cilium/cilium/pkg/k8s/apis/cilium.io" ciliumv2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" "github.com/cilium/cilium/pkg/k8s/client/clientset/versioned/scheme" "github.com/cilium/cilium/pkg/lock" + "github.com/cilium/cilium/pkg/policy/api" ) /* How many times we should retry getting the policy revisions before @@ -106,6 +109,25 @@ type client[T policy] interface { Update(ctx context.Context, networkPolicy T, opts metav1.UpdateOptions) (T, error) } +// createOrUpdate applies a generic object to the cluster, returning true if it was updated +func createOrUpdate(ctx context.Context, client *k8s.Client, obj k8s.Object) (bool, error) { + existing, err := client.GetGeneric(ctx, obj.GetNamespace(), obj.GetName(), obj) + if err != nil && !k8serrors.IsNotFound(err) { + return false, fmt.Errorf("failed to retrieve %s/%s: %w", obj.GetNamespace(), obj.GetName(), err) + } + + created, err := client.ApplyGeneric(ctx, obj) + if err != nil { + return false, fmt.Errorf("failed to create / update %s %s/%s: %w", obj.GetObjectKind().GroupVersionKind().Kind, obj.GetNamespace(), obj.GetName(), err) + } + + if existing == nil { + return true, nil + } + + return existing.GetGeneration() != created.GetGeneration(), nil +} + // CreateOrUpdatePolicy implements the generic logic to create or update a policy. func CreateOrUpdatePolicy[T policy](ctx context.Context, client client[T], obj T, mutator func(obj T) bool) (bool, error) { // Let's attempt to create the policy. We optimize the creation path @@ -141,137 +163,6 @@ func CreateOrUpdatePolicy[T policy](ctx context.Context, client client[T], obj T return true, nil } -// createOrUpdateCNP creates the CNP and updates it if it already exists. -func createOrUpdateCNP(ctx context.Context, client *k8s.Client, cnp *ciliumv2.CiliumNetworkPolicy) (bool, error) { - return CreateOrUpdatePolicy(ctx, client.CiliumClientset.CiliumV2().CiliumNetworkPolicies(cnp.GetNamespace()), - cnp, func(current *ciliumv2.CiliumNetworkPolicy) bool { - if maps.Equal(current.GetLabels(), cnp.GetLabels()) && - current.Spec.DeepEqual(cnp.Spec) && - current.Specs.DeepEqual(&cnp.Specs) { - return false - } - - current.ObjectMeta.Labels = cnp.ObjectMeta.Labels - current.Spec = cnp.Spec - current.Specs = cnp.Specs - return true - }, - ) -} - -// createOrUpdateCCNP creates the CCNP and updates it if it already exists. -func createOrUpdateCCNP(ctx context.Context, client *k8s.Client, ccnp *ciliumv2.CiliumClusterwideNetworkPolicy) (bool, error) { - return CreateOrUpdatePolicy(ctx, client.CiliumClientset.CiliumV2().CiliumClusterwideNetworkPolicies(), - ccnp, func(current *ciliumv2.CiliumClusterwideNetworkPolicy) bool { - if maps.Equal(current.GetLabels(), ccnp.GetLabels()) && - current.Spec.DeepEqual(ccnp.Spec) && - current.Specs.DeepEqual(&ccnp.Specs) { - return false - } - - current.ObjectMeta.Labels = ccnp.ObjectMeta.Labels - current.Spec = ccnp.Spec - current.Specs = ccnp.Specs - return true - }, - ) -} - -// createOrUpdateKNP creates the KNP and updates it if it already exists. -func createOrUpdateKNP(ctx context.Context, client *k8s.Client, knp *networkingv1.NetworkPolicy) (bool, error) { - return CreateOrUpdatePolicy(ctx, client.Clientset.NetworkingV1().NetworkPolicies(knp.GetNamespace()), - knp, func(current *networkingv1.NetworkPolicy) bool { - if maps.Equal(current.GetLabels(), knp.GetLabels()) && - reflect.DeepEqual(current.Spec, knp.Spec) { - return false - } - - current.ObjectMeta.Labels = knp.ObjectMeta.Labels - current.Spec = knp.Spec - return true - }, - ) -} - -// createOrUpdateCEGP creates the CEGP and updates it if it already exists. -func createOrUpdateCEGP(ctx context.Context, client *k8s.Client, cegp *ciliumv2.CiliumEgressGatewayPolicy) error { - _, err := CreateOrUpdatePolicy(ctx, client.CiliumClientset.CiliumV2().CiliumEgressGatewayPolicies(), - cegp, func(current *ciliumv2.CiliumEgressGatewayPolicy) bool { - if maps.Equal(current.GetLabels(), cegp.GetLabels()) && - current.Spec.DeepEqual(&cegp.Spec) { - return false - } - - current.ObjectMeta.Labels = cegp.ObjectMeta.Labels - current.Spec = cegp.Spec - return true - }, - ) - return err -} - -// createOrUpdateCLRP creates the CLRP and updates it if it already exists. -func createOrUpdateCLRP(ctx context.Context, client *k8s.Client, clrp *ciliumv2.CiliumLocalRedirectPolicy) error { - _, err := CreateOrUpdatePolicy(ctx, client.CiliumClientset.CiliumV2().CiliumLocalRedirectPolicies(clrp.Namespace), - clrp, func(current *ciliumv2.CiliumLocalRedirectPolicy) bool { - if maps.Equal(current.GetLabels(), clrp.GetLabels()) && - current.Spec.DeepEqual(&clrp.Spec) { - return false - } - - current.ObjectMeta.Labels = clrp.ObjectMeta.Labels - current.Spec = clrp.Spec - return true - }, - ) - return err -} - -// deleteCNP deletes a CiliumNetworkPolicy from the cluster. -func deleteCNP(ctx context.Context, client *k8s.Client, cnp *ciliumv2.CiliumNetworkPolicy) error { - if err := client.DeleteCiliumNetworkPolicy(ctx, cnp.Namespace, cnp.Name, metav1.DeleteOptions{}); err != nil { - return fmt.Errorf("%s/%s/%s policy delete failed: %w", client.ClusterName(), cnp.Namespace, cnp.Name, err) - } - - return nil -} - -// deleteCNP deletes a CiliumNetworkPolicy from the cluster. -func deleteCCNP(ctx context.Context, client *k8s.Client, ccnp *ciliumv2.CiliumClusterwideNetworkPolicy) error { - if err := client.DeleteCiliumClusterwideNetworkPolicy(ctx, ccnp.Name, metav1.DeleteOptions{}); err != nil { - return fmt.Errorf("%s/%s policy delete failed: %w", client.ClusterName(), ccnp.Name, err) - } - - return nil -} - -// deleteKNP deletes a Kubernetes NetworkPolicy from the cluster. -func deleteKNP(ctx context.Context, client *k8s.Client, knp *networkingv1.NetworkPolicy) error { - if err := client.DeleteKubernetesNetworkPolicy(ctx, knp.Namespace, knp.Name, metav1.DeleteOptions{}); err != nil { - return fmt.Errorf("%s/%s/%s policy delete failed: %w", client.ClusterName(), knp.Namespace, knp.Name, err) - } - - return nil -} - -// deleteCEGP deletes a CiliumEgressGatewayPolicy from the cluster. -func deleteCEGP(ctx context.Context, client *k8s.Client, cegp *ciliumv2.CiliumEgressGatewayPolicy) error { - if err := client.DeleteCiliumEgressGatewayPolicy(ctx, cegp.Name, metav1.DeleteOptions{}); err != nil { - return fmt.Errorf("%s/%s policy delete failed: %w", client.ClusterName(), cegp.Name, err) - } - - return nil -} - -// deleteCLRP deletes a CiliumLocalRedirectPolicy from the cluster. -func deleteCLRP(ctx context.Context, client *k8s.Client, clrp *ciliumv2.CiliumLocalRedirectPolicy) error { - if err := client.DeleteCiliumLocalRedirectPolicy(ctx, clrp.Namespace, clrp.Name, metav1.DeleteOptions{}); err != nil { - return fmt.Errorf("%s/%s/%s policy delete failed: %w", client.ClusterName(), clrp.Namespace, clrp.Name, err) - } - - return nil -} - func defaultDropReason(flow *flowpb.Flow) bool { return flow.GetDropReasonDesc() != flowpb.DropReason_DROP_REASON_UNKNOWN } @@ -288,6 +179,10 @@ func authRequiredDropReason(flow *flowpb.Flow) bool { return flow.GetDropReasonDesc() == flowpb.DropReason_AUTH_REQUIRED } +func unencryptedDropReason(flow *flowpb.Flow) bool { + return flow.GetDropReasonDesc() == flowpb.DropReason_UNENCRYPTED_TRAFFIC +} + type ExpectationsFunc func(a *Action) (egress, ingress Result) // WithExpectations sets the getExpectations test result function to use during tests @@ -335,35 +230,6 @@ func RegisterPolicy[T policy](current map[string]T, policies ...T) (map[string]T return current, nil } -// addCNPs adds one or more CiliumNetworkPolicy resources to the Test. -func (t *Test) addCNPs(cnps ...*ciliumv2.CiliumNetworkPolicy) (err error) { - t.cnps, err = RegisterPolicy(t.cnps, cnps...) - return err -} - -// addCNPs adds one or more CiliumClusterwideNetworkPolicy resources to the Test. -func (t *Test) addCCNPs(ccnps ...*ciliumv2.CiliumClusterwideNetworkPolicy) (err error) { - t.ccnps, err = RegisterPolicy(t.ccnps, ccnps...) - return err -} - -// addKNPs adds one or more K8S NetworkPolicy resources to the Test. -func (t *Test) addKNPs(policies ...*networkingv1.NetworkPolicy) (err error) { - t.knps, err = RegisterPolicy(t.knps, policies...) - return err -} - -// addCEGPs adds one or more CiliumEgressGatewayPolicy resources to the Test. -func (t *Test) addCEGPs(cegps ...*ciliumv2.CiliumEgressGatewayPolicy) (err error) { - t.cegps, err = RegisterPolicy(t.cegps, cegps...) - return err -} - -func (t *Test) addCLRPs(clrps ...*ciliumv2.CiliumLocalRedirectPolicy) (err error) { - t.clrps, err = RegisterPolicy(t.clrps, clrps...) - return err -} - func sumMap(m map[string]int) int { sum := 0 for _, v := range m { @@ -376,9 +242,18 @@ func sumMap(m map[string]int) int { // can apply or delete policies in case of connectivity test concurrency > 1 var policyApplyDeleteLock = lock.Mutex{} -// applyPolicies applies all the Test's registered network policies. -func (t *Test) applyPolicies(ctx context.Context) error { - if len(t.cnps) == 0 && len(t.ccnps) == 0 && len(t.knps) == 0 && len(t.cegps) == 0 && len(t.clrps) == 0 { +// isPolicy returns true if the object is a network policy, and thus +// should bump the policy revision. +func isPolicy(obj k8s.Object) bool { + gk := obj.GetObjectKind().GroupVersionKind().GroupKind() + return (gk == schema.GroupKind{Group: ciliumv2.CustomResourceDefinitionGroup, Kind: ciliumv2.CNPKindDefinition} || + gk == schema.GroupKind{Group: ciliumv2.CustomResourceDefinitionGroup, Kind: ciliumv2.CCNPKindDefinition} || + gk == schema.GroupKind{Group: networkingv1.GroupName, Kind: "NetworkPolicy"}) +} + +// applyResources applies all the Test's registered additional resources +func (t *Test) applyResources(ctx context.Context) error { + if len(t.resources) == 0 { return nil } @@ -397,73 +272,28 @@ func (t *Test) applyPolicies(ctx context.Context) error { // Incremented, by cluster, for every expected revision. revDeltas := map[string]int{} - // Apply all given CiliumNetworkPolicies. - for _, cnp := range t.cnps { - for _, client := range t.Context().clients.clients() { - t.Infof("📜 Applying CiliumNetworkPolicy '%s' to namespace '%s'..", cnp.Name, cnp.Namespace) - changed, err := createOrUpdateCNP(ctx, client, cnp) - if err != nil { - return fmt.Errorf("policy application failed: %w", err) - } - if changed { - revDeltas[client.ClusterName()]++ - } - } - } - // Apply all given CiliumClusterwideNetworkPolicy. - for _, ccnp := range t.ccnps { + // apply resources to all clusters + for _, obj := range t.resources { + kind := obj.GetObjectKind().GroupVersionKind().Kind for _, client := range t.Context().clients.clients() { - t.Infof("📜 Applying CiliumClusterwideNetworkPolicy '%s'..", ccnp.Name) - changed, err := createOrUpdateCCNP(ctx, client, ccnp) + t.Infof("📜 Applying %s '%s' to namespace '%s' on cluster %s..", kind, obj.GetName(), obj.GetNamespace(), client.ClusterName()) + changed, err := createOrUpdate(ctx, client, obj) if err != nil { - return fmt.Errorf("policy application failed: %w", err) - } - if changed { - revDeltas[client.ClusterName()]++ + return fmt.Errorf("failed to apply %s '%s' to namespace '%s' on cluster %s: %w", kind, obj.GetName(), obj.GetNamespace(), client.ClusterName(), err) } - } - } - // Apply all given Kubernetes Network Policies. - for _, knp := range t.knps { - for _, client := range t.Context().clients.clients() { - t.Infof("📜 Applying KubernetesNetworkPolicy '%s' to namespace '%s'..", knp.Name, knp.Namespace) - changed, err := createOrUpdateKNP(ctx, client, knp) - if err != nil { - return fmt.Errorf("policy application failed: %w", err) - } - if changed { + if changed && isPolicy(obj) { revDeltas[client.ClusterName()]++ } } } - // Apply all given Cilium Egress Gateway Policies. - for _, cegp := range t.cegps { - for _, client := range t.Context().clients.clients() { - t.Infof("📜 Applying CiliumEgressGatewayPolicy '%s' to namespace '%s'..", cegp.Name, cegp.Namespace) - if err := createOrUpdateCEGP(ctx, client, cegp); err != nil { - return fmt.Errorf("policy application failed: %w", err) - } - } - } - - // Apply all given Cilium Local Redirect Policies. - for _, clrp := range t.clrps { - for _, client := range t.Context().clients.clients() { - t.Infof("📜 Applying CiliumLocalRedirectPolicy '%s' to namespace '%s'..", clrp.Name, clrp.Namespace) - if err := createOrUpdateCLRP(ctx, client, clrp); err != nil { - return fmt.Errorf("policy application failed: %w", err) - } - } - } - // Register a finalizer with the Test immediately to enable cleanup. // If we return a cleanup closure from this function, cleanup cannot be // performed if the user cancels during the policy revision wait time. t.finalizers = append(t.finalizers, func(ctx context.Context) error { - if err := t.deletePolicies(ctx); err != nil { + if err := t.deleteResources(ctx); err != nil { t.CiliumLogs(ctx) return err } @@ -484,29 +314,17 @@ func (t *Test) applyPolicies(ctx context.Context) error { } } - if len(t.cnps) > 0 { - t.Debugf("📜 Successfully applied %d CiliumNetworkPolicies", len(t.cnps)) - } - if len(t.ccnps) > 0 { - t.Debugf("📜 Successfully applied %d CiliumClusterwideNetworkPolicies", len(t.ccnps)) - } - if len(t.knps) > 0 { - t.Debugf("📜 Successfully applied %d K8S NetworkPolicies", len(t.knps)) - } - if len(t.cegps) > 0 { - t.Debugf("📜 Successfully applied %d CiliumEgressGatewayPolicies", len(t.cegps)) - } - - if len(t.clrps) > 0 { - t.Debugf("📜 Successfully applied %d CiliumLocalRedirectPolicies", len(t.clrps)) + if len(t.resources) > 0 { + t.Debugf("📜 Successfully applied %d additional resources", len(t.resources)) } return nil } -// deletePolicies deletes a given set of network policies from the cluster. -func (t *Test) deletePolicies(ctx context.Context) error { - if len(t.cnps) == 0 && len(t.ccnps) == 0 && len(t.knps) == 0 && len(t.cegps) == 0 && len(t.clrps) == 0 { +// deleteResources deletes the previously-created set of resources that +// belong to this test. +func (t *Test) deleteResources(ctx context.Context) error { + if len(t.resources) == 0 { return nil } @@ -523,84 +341,30 @@ func (t *Test) deletePolicies(ctx context.Context) error { } revDeltas := map[string]int{} - // Delete all the Test's CNPs from all clients. - for _, cnp := range t.cnps { - t.Infof("📜 Deleting CiliumNetworkPolicy '%s' from namespace '%s'..", cnp.Name, cnp.Namespace) - for _, client := range t.Context().clients.clients() { - if err := deleteCNP(ctx, client, cnp); err != nil { - return fmt.Errorf("deleting CiliumNetworkPolicy: %w", err) - } - revDeltas[client.ClusterName()]++ - } - } - - // Delete all the Test's CCNPs from all clients. - for _, ccnp := range t.ccnps { - t.Infof("📜 Deleting CiliumClusterwideNetworkPolicy '%s'..", ccnp.Name) - for _, client := range t.Context().clients.clients() { - if err := deleteCCNP(ctx, client, ccnp); err != nil { - return fmt.Errorf("deleting CiliumClusterwideNetworkPolicy: %w", err) - } - revDeltas[client.ClusterName()]++ - } - } - - // Delete all the Test's KNPs from all clients. - for _, knp := range t.knps { - t.Infof("📜 Deleting K8S NetworkPolicy '%s' from namespace '%s'..", knp.Name, knp.Namespace) - for _, client := range t.Context().clients.clients() { - if err := deleteKNP(ctx, client, knp); err != nil { - return fmt.Errorf("deleting K8S NetworkPolicy: %w", err) - } - revDeltas[client.ClusterName()]++ - } - } - - // Delete all the Test's CEGPs from all clients. - for _, cegp := range t.cegps { - t.Infof("📜 Deleting CiliumEgressGatewayPolicy '%s' from namespace '%s'..", cegp.Name, cegp.Namespace) + for _, obj := range t.resources { + kind := obj.GetObjectKind().GroupVersionKind().Kind for _, client := range t.Context().clients.clients() { - if err := deleteCEGP(ctx, client, cegp); err != nil { - return fmt.Errorf("deleting CiliumEgressGatewayPolicy: %w", err) + t.Infof("📜 Deleting %s '%s' in namespace '%s' on cluster %s..", kind, obj.GetName(), obj.GetNamespace(), client.ClusterName()) + err := client.DeleteGeneric(ctx, obj) + if err != nil { + return fmt.Errorf("failed to delete %s '%s' in namespace '%s' on cluster %s: %w", kind, obj.GetName(), obj.GetNamespace(), client.ClusterName(), err) } - } - } - // Delete all the Test's CLRPs from all clients. - for _, clrp := range t.clrps { - t.Infof("📜 Deleting CiliumLocalRedirectPolicy '%s' from namespace '%s'..", clrp.Name, clrp.Namespace) - for _, client := range t.Context().clients.clients() { - if err := deleteCLRP(ctx, client, clrp); err != nil { - return fmt.Errorf("deleting CiliumLocalRedirectPolicy: %w", err) + if isPolicy(obj) { + revDeltas[client.ClusterName()]++ } } } - if len(t.cnps) != 0 || len(t.ccnps) != 0 || len(t.knps) != 0 || len(t.clrps) != 0 { + if len(revDeltas) > 0 { // Wait for policies to be deleted on all Cilium nodes. if err := t.waitCiliumPolicyRevisions(ctx, revs, revDeltas); err != nil { - return fmt.Errorf("timed out removing policies on Cilium agents: %w", err) + return fmt.Errorf("timed out waiting for policy updates to be processed on Cilium agents: %w", err) } } - if len(t.cnps) > 0 { - t.Debugf("📜 Successfully deleted %d CiliumNetworkPolicies", len(t.cnps)) - } - - if len(t.ccnps) > 0 { - t.Debugf("📜 Successfully deleted %d CiliumClusterwideNetworkPolicies", len(t.ccnps)) - } - - if len(t.knps) > 0 { - t.Debugf("📜 Successfully deleted %d K8S NetworkPolicy", len(t.knps)) - } - - if len(t.cegps) > 0 { - t.Debugf("📜 Successfully deleted %d CiliumEgressGatewayPolicies", len(t.cegps)) - } - - if len(t.clrps) > 0 { - t.Debugf("📜 Successfully deleted %d CiliumLocalRedirectPolicies", len(t.clrps)) + if len(t.resources) > 0 { + t.Debugf("📜 Successfully deleted %d resources", len(t.resources)) } return nil @@ -610,7 +374,7 @@ func (t *Test) deletePolicies(ctx context.Context) error { // filter is applied on each line of output. func (t *Test) CiliumLogs(ctx context.Context) { for _, pod := range t.Context().ciliumPods { - log, err := pod.K8sClient.CiliumLogs(ctx, pod.Pod.Namespace, pod.Pod.Name, t.startTime) + log, err := pod.K8sClient.CiliumLogs(ctx, pod.Pod.Namespace, pod.Pod.Name, t.startTime, false) if err != nil { t.Fatalf("Error reading Cilium logs: %s", err) } @@ -618,58 +382,155 @@ func (t *Test) CiliumLogs(ctx context.Context) { } } -// ParsePolicyYAML decodes a yaml file into a slice of policies. -func ParsePolicyYAML[T runtime.Object](input string, scheme *runtime.Scheme) (output []T, err error) { - if input == "" { - return nil, nil - } - - yamls := strings.Split(input, "\n---") +// tweakPolicy adjusts a test-dependent resource to insert the namespace +// in known objects. +func (t *Test) tweakPolicy(in *unstructured.Unstructured) *unstructured.Unstructured { + group := in.GroupVersionKind().Group + kind := in.GroupVersionKind().Kind - for _, yaml := range yamls { - if strings.TrimSpace(yaml) == "" { - continue + var tweaked runtime.Object + if group == ciliumv2.CustomResourceDefinitionGroup && kind == ciliumv2.CNPKindDefinition { + t.WithFeatureRequirements(features.RequireEnabled(features.CNP)) + cnp := ciliumv2.CiliumNetworkPolicy{} + if err := convertInto(in, &cnp); err != nil { + t.Fatalf("could not parse CiliumNetworkPolicy: %v", err) + return nil + } + if cnp.Namespace == "" { + cnp.Namespace = t.ctx.params.TestNamespace } + configureNamespaceInPolicySpec(cnp.Spec, t.ctx.params.TestNamespace) + tweaked = &cnp + } - obj, kind, err := serializer.NewCodecFactory(scheme, serializer.EnableStrict).UniversalDeserializer().Decode([]byte(yaml), nil, nil) - if err != nil { - return nil, fmt.Errorf("decoding yaml file: %s\nerror: %w", yaml, err) + if group == ciliumv2.CustomResourceDefinitionGroup && kind == ciliumv2.CCNPKindDefinition { + t.WithFeatureRequirements(features.RequireEnabled(features.CCNP)) + ccnp := ciliumv2.CiliumClusterwideNetworkPolicy{} + if err := convertInto(in, &ccnp); err != nil { + t.Fatalf("could not parse CiliumClusterwideNetworkPolicy: %v", err) + return nil } + configureNamespaceInPolicySpec(ccnp.Spec, t.ctx.params.TestNamespace) + tweaked = &ccnp + } - switch policy := obj.(type) { - case T: - output = append(output, policy) - default: - return nil, fmt.Errorf("unknown type '%s' in: %s", kind.Kind, yaml) + if group == networkingv1.GroupName && kind == "NetworkPolicy" { + t.WithFeatureRequirements(features.RequireEnabled(features.KNP)) + knp := networkingv1.NetworkPolicy{} + if err := convertInto(in, &knp); err != nil { + t.Fatalf("could not parse NetworkPolicy: %v", err) + return nil } + configureNamespaceInKNP(&knp, t.ctx.params.TestNamespace) + tweaked = &knp } - return output, nil -} + if tweaked == nil { + return in + } -// parseCiliumPolicyYAML decodes policy yaml into a slice of CiliumNetworkPolicies. -func parseCiliumPolicyYAML(policy string) (cnps []*ciliumv2.CiliumNetworkPolicy, err error) { - return ParsePolicyYAML[*ciliumv2.CiliumNetworkPolicy](policy, scheme.Scheme) + out := unstructured.Unstructured{} + if err := convertInto(tweaked, &out); err != nil { + t.Fatalf("could not convert tweaked object") // unreachable + return nil + } + return &out } -// parseCiliumClusterwidePolicyYAML decodes policy yaml into a slice of CiliumClusterwideNetworkPolicy. -func parseCiliumClusterwidePolicyYAML(policy string) (cnps []*ciliumv2.CiliumClusterwideNetworkPolicy, err error) { - return ParsePolicyYAML[*ciliumv2.CiliumClusterwideNetworkPolicy](policy, scheme.Scheme) +func configureNamespaceInPolicySpec(spec *api.Rule, namespace string) { + if spec == nil { + return + } + + for _, k := range []string{ + k8sConst.PodNamespaceLabel, + KubernetesSourcedLabelPrefix + k8sConst.PodNamespaceLabel, + AnySourceLabelPrefix + k8sConst.PodNamespaceLabel, + } { + for _, e := range spec.Egress { + for _, es := range e.ToEndpoints { + if n, ok := es.MatchLabels[k]; ok && n == defaults.ConnectivityCheckNamespace { + es.MatchLabels[k] = namespace + } + } + } + for _, e := range spec.Ingress { + for _, es := range e.FromEndpoints { + if n, ok := es.MatchLabels[k]; ok && n == defaults.ConnectivityCheckNamespace { + es.MatchLabels[k] = namespace + } + } + } + + for _, e := range spec.EgressDeny { + for _, es := range e.ToEndpoints { + if n, ok := es.MatchLabels[k]; ok && n == defaults.ConnectivityCheckNamespace { + es.MatchLabels[k] = namespace + } + } + } + + for _, e := range spec.IngressDeny { + for _, es := range e.FromEndpoints { + if n, ok := es.MatchLabels[k]; ok && n == defaults.ConnectivityCheckNamespace { + es.MatchLabels[k] = namespace + } + } + } + } } -// parseK8SPolicyYAML decodes policy yaml into a slice of K8S NetworkPolicies. -func parseK8SPolicyYAML(policy string) (policies []*networkingv1.NetworkPolicy, err error) { - return ParsePolicyYAML[*networkingv1.NetworkPolicy](policy, clientsetscheme.Scheme) +func configureNamespaceInKNP(pol *networkingv1.NetworkPolicy, namespace string) { + pol.Namespace = namespace + + if pol.Spec.Size() != 0 { + for _, k := range []string{ + k8sConst.PodNamespaceLabel, + KubernetesSourcedLabelPrefix + k8sConst.PodNamespaceLabel, + AnySourceLabelPrefix + k8sConst.PodNamespaceLabel, + } { + for _, e := range pol.Spec.Egress { + for _, es := range e.To { + if es.PodSelector != nil { + if n, ok := es.PodSelector.MatchLabels[k]; ok && n == defaults.ConnectivityCheckNamespace { + es.PodSelector.MatchLabels[k] = namespace + } + } + if es.NamespaceSelector != nil { + if n, ok := es.NamespaceSelector.MatchLabels[k]; ok && n == defaults.ConnectivityCheckNamespace { + es.NamespaceSelector.MatchLabels[k] = namespace + } + } + } + } + for _, e := range pol.Spec.Ingress { + for _, es := range e.From { + if es.PodSelector != nil { + if n, ok := es.PodSelector.MatchLabels[k]; ok && n == defaults.ConnectivityCheckNamespace { + es.PodSelector.MatchLabels[k] = namespace + } + } + if es.NamespaceSelector != nil { + if n, ok := es.NamespaceSelector.MatchLabels[k]; ok && n == defaults.ConnectivityCheckNamespace { + es.NamespaceSelector.MatchLabels[k] = namespace + } + } + } + } + } + } } -// parseCiliumEgressGatewayPolicyYAML decodes policy yaml into a slice of -// CiliumEgressGatewayPolicies. -func parseCiliumEgressGatewayPolicyYAML(policy string) (cegps []*ciliumv2.CiliumEgressGatewayPolicy, err error) { - return ParsePolicyYAML[*ciliumv2.CiliumEgressGatewayPolicy](policy, scheme.Scheme) +// convertInto converts an object using JSON +func convertInto(input, output runtime.Object) error { + b, err := json.Marshal(input) + if err != nil { + return err // unreachable + } + return parseInto(b, output) } -// parseCiliumLocalRedirectPolicyYAML decodes policy yaml into a slice of -// CiliumLocalRedirectPolicies. -func parseCiliumLocalRedirectPolicyYAML(policy string) (clrp []*ciliumv2.CiliumLocalRedirectPolicy, err error) { - return ParsePolicyYAML[*ciliumv2.CiliumLocalRedirectPolicy](policy, scheme.Scheme) +func parseInto(b []byte, output runtime.Object) error { + _, _, err := serializer.NewCodecFactory(scheme.Scheme, serializer.EnableStrict).UniversalDeserializer().Decode(b, nil, output) + return err } diff --git a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/result.go b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/result.go index d295c40d0d..de94e6b555 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/result.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/result.go @@ -181,6 +181,13 @@ var ( ExitCode: ExitAnyError, } + ResultEgressUnencryptedDrop = Result{ + Drop: true, + EgressDrop: true, + DropReasonFunc: unencryptedDropReason, + ExitCode: ExitCurlTimeout, + } + // ResultDropCurlTimeout expects a dropped flow and a failed command. ResultDropCurlTimeout = Result{ Drop: true, diff --git a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/test.go b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/test.go index a89c3af020..c6a5f54961 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/test.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/test.go @@ -7,6 +7,7 @@ import ( "bytes" "context" _ "embed" + "errors" "fmt" "io" "net" @@ -21,16 +22,17 @@ import ( "github.com/cloudflare/cfssl/signer" "github.com/cloudflare/cfssl/signer/local" corev1 "k8s.io/api/core/v1" - networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/util/yaml" "github.com/cilium/cilium/cilium-cli/defaults" + "github.com/cilium/cilium/cilium-cli/k8s" "github.com/cilium/cilium/cilium-cli/sysdump" "github.com/cilium/cilium/cilium-cli/utils/features" k8sConst "github.com/cilium/cilium/pkg/k8s/apis/cilium.io" ciliumv2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" "github.com/cilium/cilium/pkg/lock" - "github.com/cilium/cilium/pkg/policy/api" "github.com/cilium/cilium/pkg/versioncheck" ) @@ -62,10 +64,7 @@ func NewTest(name string, verbose bool, debug bool) *Test { test := &Test{ name: name, scenarios: make(map[Scenario][]*Action), - cnps: make(map[string]*ciliumv2.CiliumNetworkPolicy), - ccnps: make(map[string]*ciliumv2.CiliumClusterwideNetworkPolicy), - knps: make(map[string]*networkingv1.NetworkPolicy), - cegps: make(map[string]*ciliumv2.CiliumEgressGatewayPolicy), + resources: []k8s.Object{}, clrps: make(map[string]*ciliumv2.CiliumLocalRedirectPolicy), logBuf: &bytes.Buffer{}, // maintain internal buffer by default conditionFn: nil, @@ -110,21 +109,14 @@ type Test struct { // Needs to be stored as a list, these are implemented in another package. scenariosSkipped []Scenario - // Policies active during this test. - cnps map[string]*ciliumv2.CiliumNetworkPolicy - - // Cilium Clusterwide Network Policies active during this test. - ccnps map[string]*ciliumv2.CiliumClusterwideNetworkPolicy - - // Kubernetes Network Policies active during this test. - knps map[string]*networkingv1.NetworkPolicy - - // Cilium Egress Gateway Policies active during this test. - cegps map[string]*ciliumv2.CiliumEgressGatewayPolicy - // Cilium Local Redirect Policies active during this test. clrps map[string]*ciliumv2.CiliumLocalRedirectPolicy + // k8s resources that should be created before the test run, and removed afterwards. + // If any of these correspond to a network policy, this will wait for the policy revision + // to be incremented. + resources []k8s.Object + // Secrets that have to be present during the test. secrets map[string]*corev1.Secret @@ -160,8 +152,8 @@ type Test struct { } func (t *Test) String() string { - return fmt.Sprintf("", - t.name, len(t.scenarios), len(t.cnps), len(t.ccnps), t.expectFunc) + return fmt.Sprintf("", + t.name, len(t.scenarios), len(t.resources), t.expectFunc) } // Name returns the name of the test. @@ -211,7 +203,7 @@ func (t *Test) setup(ctx context.Context) error { } // Apply CNPs & KNPs to the cluster. - if err := t.applyPolicies(ctx); err != nil { + if err := t.applyResources(ctx); err != nil { t.CiliumLogs(ctx) return fmt.Errorf("applying network policies: %w", err) } @@ -393,49 +385,6 @@ func (t *Test) Run(ctx context.Context, index int) error { return nil } -func configureNamespaceInPolicySpec(spec *api.Rule, namespace string) { - if spec == nil { - return - } - - for _, k := range []string{ - k8sConst.PodNamespaceLabel, - KubernetesSourcedLabelPrefix + k8sConst.PodNamespaceLabel, - AnySourceLabelPrefix + k8sConst.PodNamespaceLabel, - } { - for _, e := range spec.Egress { - for _, es := range e.ToEndpoints { - if n, ok := es.MatchLabels[k]; ok && n == defaults.ConnectivityCheckNamespace { - es.MatchLabels[k] = namespace - } - } - } - for _, e := range spec.Ingress { - for _, es := range e.FromEndpoints { - if n, ok := es.MatchLabels[k]; ok && n == defaults.ConnectivityCheckNamespace { - es.MatchLabels[k] = namespace - } - } - } - - for _, e := range spec.EgressDeny { - for _, es := range e.ToEndpoints { - if n, ok := es.MatchLabels[k]; ok && n == defaults.ConnectivityCheckNamespace { - es.MatchLabels[k] = namespace - } - } - } - - for _, e := range spec.IngressDeny { - for _, es := range e.FromEndpoints { - if n, ok := es.MatchLabels[k]; ok && n == defaults.ConnectivityCheckNamespace { - es.MatchLabels[k] = namespace - } - } - } - } -} - // WithCondition takes a function containing condition check logic that // returns true if the test needs to be run, and false otherwise. If // WithCondition gets called multiple times, all the conditions need to be @@ -445,29 +394,41 @@ func (t *Test) WithCondition(fn func() bool) *Test { return t } +// WithResources registers the list of one or more YAML-defined +// Kubernetes resources (e.g. NetworkPolicy, etc.) +// +// # For certain well-known types, known references to the namespace are mutated +// +// If the resource has a namepace of "cilium-test", that is mutated +// to the (serialized) namespace of the individual scenario. +func (t *Test) WithResources(spec string) *Test { + buf := bytes.Buffer{} + buf.WriteString(spec) + decoder := yaml.NewYAMLOrJSONDecoder(&buf, 4096) + + for { + u := unstructured.Unstructured{} + if err := decoder.Decode(&u); err != nil { + if errors.Is(err, io.EOF) { + break + } + t.Fatalf("Parsing resource YAML: %s", err) + } + + if u.GetNamespace() == defaults.ConnectivityCheckNamespace { + u.SetNamespace(t.ctx.params.TestNamespace) + } + t.resources = append(t.resources, t.tweakPolicy(&u)) + } + return t +} + // WithCiliumPolicy takes a string containing a YAML policy document and adds // the polic(y)(ies) to the scope of the Test, to be applied when the test // starts running. When calling this method, note that the CNP enabled feature // // requirement is applied directly here. func (t *Test) WithCiliumPolicy(policy string) *Test { - pl, err := parseCiliumPolicyYAML(policy) - if err != nil { - t.Fatalf("Parsing policy YAML: %s", err) - } - - // Change the default test namespace as required. - for i := range pl { - pl[i].Namespace = t.ctx.params.TestNamespace - configureNamespaceInPolicySpec(pl[i].Spec, t.ctx.params.TestNamespace) - } - - if err := t.addCNPs(pl...); err != nil { - t.Fatalf("Adding CNPs to policy context: %s", err) - } - - t.WithFeatureRequirements(features.RequireEnabled(features.CNP)) - - return t + return t.WithResources(policy) } // WithCiliumClusterwidePolicy takes a string containing a YAML policy document @@ -475,23 +436,7 @@ func (t *Test) WithCiliumPolicy(policy string) *Test { // when the test starts running. When calling this method, note that the CCNP // enabled feature requirement is applied directly here. func (t *Test) WithCiliumClusterwidePolicy(policy string) *Test { - pl, err := parseCiliumClusterwidePolicyYAML(policy) - if err != nil { - t.Fatalf("Parsing policy YAML: %s", err) - } - - // Change the default test namespace as required. - for i := range pl { - configureNamespaceInPolicySpec(pl[i].Spec, t.ctx.params.TestNamespace) - } - - if err := t.addCCNPs(pl...); err != nil { - t.Fatalf("Adding CCNPs to policy context: %s", err) - } - - t.WithFeatureRequirements(features.RequireEnabled(features.CCNP)) - - return t + return t.WithResources(policy) } // WithK8SPolicy takes a string containing a YAML policy document and adds @@ -499,61 +444,7 @@ func (t *Test) WithCiliumClusterwidePolicy(policy string) *Test { // starts running. When calling this method, note that the KNP enabled feature // requirement is applied directly here. func (t *Test) WithK8SPolicy(policy string) *Test { - pl, err := parseK8SPolicyYAML(policy) - if err != nil { - t.Fatalf("Parsing K8S policy YAML: %s", err) - } - - // Change the default test namespace as required. - for i := range pl { - pl[i].Namespace = t.ctx.params.TestNamespace - - if pl[i].Spec.Size() != 0 { - for _, k := range []string{ - k8sConst.PodNamespaceLabel, - KubernetesSourcedLabelPrefix + k8sConst.PodNamespaceLabel, - AnySourceLabelPrefix + k8sConst.PodNamespaceLabel, - } { - for _, e := range pl[i].Spec.Egress { - for _, es := range e.To { - if es.PodSelector != nil { - if n, ok := es.PodSelector.MatchLabels[k]; ok && n == defaults.ConnectivityCheckNamespace { - es.PodSelector.MatchLabels[k] = t.ctx.params.TestNamespace - } - } - if es.NamespaceSelector != nil { - if n, ok := es.NamespaceSelector.MatchLabels[k]; ok && n == defaults.ConnectivityCheckNamespace { - es.NamespaceSelector.MatchLabels[k] = t.ctx.params.TestNamespace - } - } - } - } - for _, e := range pl[i].Spec.Ingress { - for _, es := range e.From { - if es.PodSelector != nil { - if n, ok := es.PodSelector.MatchLabels[k]; ok && n == defaults.ConnectivityCheckNamespace { - es.PodSelector.MatchLabels[k] = t.ctx.params.TestNamespace - } - } - if es.NamespaceSelector != nil { - if n, ok := es.NamespaceSelector.MatchLabels[k]; ok && n == defaults.ConnectivityCheckNamespace { - es.NamespaceSelector.MatchLabels[k] = t.ctx.params.TestNamespace - } - } - } - } - } - } - } - - if err := t.addKNPs(pl...); err != nil { - t.Fatalf("Adding K8S Network Policies to policy context: %s", err) - } - - // It is implicit that KNP should be enabled. - t.WithFeatureRequirements(features.RequireEnabled(features.KNP)) - - return t + return t.WithResources(policy) } // CiliumLocalRedirectPolicyParams is used to configure a CiliumLocalRedirectPolicy template. @@ -572,21 +463,18 @@ type CiliumLocalRedirectPolicyParams struct { } func (t *Test) WithCiliumLocalRedirectPolicy(params CiliumLocalRedirectPolicyParams) *Test { - pl, err := parseCiliumLocalRedirectPolicyYAML(params.Policy) - if err != nil { + pl := ciliumv2.CiliumLocalRedirectPolicy{} + if err := parseInto([]byte(params.Policy), &pl); err != nil { t.Fatalf("Parsing local redirect policy YAML: %s", err) } - for i := range pl { - pl[i].Namespace = t.ctx.params.TestNamespace - pl[i].Name = params.Name - pl[i].Spec.RedirectFrontend.AddressMatcher.IP = params.FrontendIP - pl[i].Spec.SkipRedirectFromBackend = params.SkipRedirectFromBackend - } + pl.Namespace = t.ctx.params.TestNamespace + pl.Name = params.Name + pl.Spec.RedirectFrontend.AddressMatcher.IP = params.FrontendIP + pl.Spec.SkipRedirectFromBackend = params.SkipRedirectFromBackend - if err := t.addCLRPs(pl...); err != nil { - t.Fatalf("Adding CLRPs to cilium local redirect policy context: %s", err) - } + t.resources = append(t.resources, &pl) + t.clrps[params.Name] = &pl t.WithFeatureRequirements(features.RequireEnabled(features.LocalRedirectPolicy)) @@ -622,59 +510,55 @@ type CiliumEgressGatewayPolicyParams struct { // note that the egress gateway enabled feature requirement is applied directly // here. func (t *Test) WithCiliumEgressGatewayPolicy(params CiliumEgressGatewayPolicyParams) *Test { - pl, err := parseCiliumEgressGatewayPolicyYAML(egressGatewayPolicyYAML) - if err != nil { - t.Fatalf("Parsing policy YAML: %s", err) - } - - for i := range pl { - // Change the default test namespace as required. - for _, k := range []string{ - k8sConst.PodNamespaceLabel, - KubernetesSourcedLabelPrefix + k8sConst.PodNamespaceLabel, - AnySourceLabelPrefix + k8sConst.PodNamespaceLabel, - } { - for _, e := range pl[i].Spec.Selectors { - ps := e.PodSelector - if n, ok := ps.MatchLabels[k]; ok && n == defaults.ConnectivityCheckNamespace { - ps.MatchLabels[k] = t.ctx.params.TestNamespace - } + pl := ciliumv2.CiliumEgressGatewayPolicy{} + if err := parseInto([]byte(egressGatewayPolicyYAML), &pl); err != nil { + t.Fatalf("Parsing EgressGatewayPolicy: %s", err) + } + + // Change the default test namespace as required. + for _, k := range []string{ + k8sConst.PodNamespaceLabel, + KubernetesSourcedLabelPrefix + k8sConst.PodNamespaceLabel, + AnySourceLabelPrefix + k8sConst.PodNamespaceLabel, + } { + for _, e := range pl.Spec.Selectors { + ps := e.PodSelector + if n, ok := ps.MatchLabels[k]; ok && n == defaults.ConnectivityCheckNamespace { + ps.MatchLabels[k] = t.ctx.params.TestNamespace } } + } - // Set the policy name - pl[i].Name = params.Name - - // Set the pod selector - pl[i].Spec.Selectors[0].PodSelector.MatchLabels["kind"] = params.PodSelectorKind + // Set the policy name + pl.Name = params.Name - // Set the egress gateway node - egressGatewayNode := t.EgressGatewayNode() - if egressGatewayNode == "" { - t.Fatalf("Cannot find egress gateway node") - } + // Set the pod selector + pl.Spec.Selectors[0].PodSelector.MatchLabels["kind"] = params.PodSelectorKind - pl[i].Spec.EgressGateway.NodeSelector.MatchLabels["kubernetes.io/hostname"] = egressGatewayNode + // Set the egress gateway node + egressGatewayNode := t.EgressGatewayNode() + if egressGatewayNode == "" { + t.Fatalf("Cannot find egress gateway node") + } - // Set the excluded CIDRs - pl[i].Spec.ExcludedCIDRs = []ciliumv2.IPv4CIDR{} + pl.Spec.EgressGateway.NodeSelector.MatchLabels["kubernetes.io/hostname"] = egressGatewayNode - switch params.ExcludedCIDRsConf { - case ExternalNodeExcludedCIDRs: - for _, nodeWithoutCiliumIP := range t.Context().params.NodesWithoutCiliumIPs { - if parsedIP := net.ParseIP(nodeWithoutCiliumIP.IP); parsedIP.To4() == nil { - continue - } + // Set the excluded CIDRs + pl.Spec.ExcludedCIDRs = []ciliumv2.IPv4CIDR{} - cidr := ciliumv2.IPv4CIDR(fmt.Sprintf("%s/32", nodeWithoutCiliumIP.IP)) - pl[i].Spec.ExcludedCIDRs = append(pl[i].Spec.ExcludedCIDRs, cidr) + switch params.ExcludedCIDRsConf { + case ExternalNodeExcludedCIDRs: + for _, nodeWithoutCiliumIP := range t.Context().params.NodesWithoutCiliumIPs { + if parsedIP := net.ParseIP(nodeWithoutCiliumIP.IP); parsedIP.To4() == nil { + continue } + + cidr := ciliumv2.IPv4CIDR(fmt.Sprintf("%s/32", nodeWithoutCiliumIP.IP)) + pl.Spec.ExcludedCIDRs = append(pl.Spec.ExcludedCIDRs, cidr) } } - if err := t.addCEGPs(pl...); err != nil { - t.Fatalf("Adding CEGPs to cilium egress gateway policy context: %s", err) - } + t.resources = append(t.resources, &pl) t.WithFeatureRequirements(features.RequireEnabled(features.EgressGateway)) @@ -949,16 +833,6 @@ func (t *Test) collectSysdump() { func (t *Test) ForEachIPFamily(do func(features.IPFamily)) { ipFams := []features.IPFamily{features.IPFamilyV4, features.IPFamilyV6} - // The per-endpoint routes feature is broken with IPv6 on < v1.14 when there - // are any netpols installed (https://github.com/cilium/cilium/issues/23852 - // and https://github.com/cilium/cilium/issues/23910). - if f, ok := t.Context().Feature(features.EndpointRoutes); ok && - f.Enabled && (len(t.cnps) > 0 || len(t.knps) > 0) && - versioncheck.MustCompile("<1.14.0")(t.Context().CiliumVersion) { - - ipFams = []features.IPFamily{features.IPFamilyV4} - } - for _, ipFam := range ipFams { switch ipFam { case features.IPFamilyV4: @@ -979,18 +853,6 @@ func (t *Test) CertificateCAs() map[string][]byte { return t.certificateCAs } -func (t *Test) CiliumNetworkPolicies() map[string]*ciliumv2.CiliumNetworkPolicy { - return t.cnps -} - -func (t *Test) CiliumClusterwideNetworkPolicies() map[string]*ciliumv2.CiliumClusterwideNetworkPolicy { - return t.ccnps -} - -func (t *Test) KubernetesNetworkPolicies() map[string]*networkingv1.NetworkPolicy { - return t.knps -} - func (t *Test) CiliumLocalRedirectPolicies() map[string]*ciliumv2.CiliumLocalRedirectPolicy { return t.clrps } diff --git a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/wait.go b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/wait.go index 74c4a8f628..e0c80839cd 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/wait.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/check/wait.go @@ -23,7 +23,6 @@ import ( "github.com/cilium/cilium/cilium-cli/k8s" "github.com/cilium/cilium/cilium-cli/utils/features" "github.com/cilium/cilium/cilium-cli/utils/wait" - "github.com/cilium/cilium/pkg/inctimer" ) const ( @@ -48,7 +47,7 @@ func WaitForDeployment(ctx context.Context, log Logger, client *k8s.Client, name log.Debugf("[%s] Deployment %s/%s is not yet ready: %s", client.ClusterName(), namespace, name, err) select { - case <-inctimer.After(PollInterval): + case <-time.After(PollInterval): case <-ctx.Done(): return fmt.Errorf("timeout reached waiting for deployment %s/%s to become ready (last error: %w)", namespace, name, err) @@ -71,7 +70,7 @@ func WaitForDaemonSet(ctx context.Context, log Logger, client *k8s.Client, names log.Debugf("[%s] DaemonSet %s/%s is not yet ready: %s", client.ClusterName(), namespace, name, err) select { - case <-inctimer.After(PollInterval): + case <-time.After(PollInterval): case <-ctx.Done(): return fmt.Errorf("timeout reached waiting for DaemonSet %s/%s to become ready (last error: %w)", namespace, name, err) @@ -103,7 +102,7 @@ func WaitForPodDNS(ctx context.Context, log Logger, src, dst Pod) error { src.K8sClient.ClusterName(), target, src.Name(), dst.Name(), err, stdout.String()) select { - case <-inctimer.After(PollInterval): + case <-time.After(PollInterval): case <-ctx.Done(): return fmt.Errorf("timeout reached waiting for lookup for %s from pod %s to server on pod %s to succeed (last error: %w)", target, src.Name(), dst.Name(), err, @@ -131,7 +130,7 @@ func WaitForCoreDNS(ctx context.Context, log Logger, client Pod) error { client.K8sClient.ClusterName(), target, client.Name(), err, stdout.String()) select { - case <-inctimer.After(PollInterval): + case <-time.After(PollInterval): case <-ctx.Done(): return fmt.Errorf("timeout reached waiting for lookup for %s from pod %s to succeed (last error: %w)", target, client.Name(), err) @@ -154,7 +153,7 @@ func WaitForServiceRetrieval(ctx context.Context, log Logger, client *k8s.Client log.Debugf("[%s] Failed to retrieve Service %s/%s: %s", client.ClusterName(), namespace, name, err) select { - case <-inctimer.After(PollInterval): + case <-time.After(PollInterval): case <-ctx.Done(): return Service{}, fmt.Errorf("timeout reached waiting for service %s/%s to be retrieved (last error: %w)", namespace, name, err) @@ -166,7 +165,7 @@ func WaitForServiceRetrieval(ctx context.Context, log Logger, client *k8s.Client func WaitForService(ctx context.Context, log Logger, client Pod, service Service) error { log.Logf("⌛ [%s] Waiting for Service %s to become ready...", client.K8sClient.ClusterName(), service.Name()) - ctx, cancel := context.WithTimeout(ctx, ShortTimeout) + ctx, cancel := context.WithTimeout(ctx, 2*ShortTimeout) defer cancel() if service.Service.Spec.ClusterIP == corev1.ClusterIPNone { @@ -197,7 +196,7 @@ func WaitForService(ctx context.Context, log Logger, client Pod, service Service client.K8sClient.ClusterName(), service.Name(), err, stdout.String()) select { - case <-inctimer.After(PollInterval): + case <-time.After(PollInterval): case <-ctx.Done(): return fmt.Errorf("timeout reached waiting for service %s (last error: %w)", service.Name(), err) } @@ -228,7 +227,7 @@ func WaitForServiceEndpoints(ctx context.Context, log Logger, agent Pod, service agent.K8sClient.ClusterName(), service.Name(), agent.Name(), err) select { - case <-inctimer.After(PollInterval): + case <-time.After(PollInterval): case <-ctx.Done(): return fmt.Errorf("timeout reached waiting for service %s to appear in Cilium pod %s (last error: %w)", service.Name(), agent.Name(), err) @@ -308,7 +307,7 @@ func WaitForNodePorts(ctx context.Context, log Logger, client Pod, nodeIP string client.K8sClient.ClusterName(), nodeIP, nodePort, service.Name(), err, stdout.String()) select { - case <-inctimer.After(PollInterval): + case <-time.After(PollInterval): case <-ctx.Done(): return fmt.Errorf("timeout reached waiting for NodePort %s:%d (%s) (last error: %w)", nodeIP, nodePort, service.Name(), err) @@ -336,7 +335,7 @@ func WaitForIPCache(ctx context.Context, log Logger, agent Pod, pods []Pod) erro log.Debugf("[%s] Error checking pod IPs in IPCache: %s", agent.K8sClient.ClusterName(), err) select { - case <-inctimer.After(PollInterval): + case <-time.After(PollInterval): case <-ctx.Done(): return fmt.Errorf("timeout reached waiting for pod IPs to be in IPCache of Cilium pod %s (last error: %w)", agent.Name(), err) diff --git a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/sniff/sniffer.go b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/sniff/sniffer.go index bcba0dc1f3..f035d74a02 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/sniff/sniffer.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/sniff/sniffer.go @@ -13,7 +13,6 @@ import ( "github.com/cilium/cilium/cilium-cli/connectivity/check" "github.com/cilium/cilium/cilium-cli/utils/lock" - "github.com/cilium/cilium/pkg/inctimer" ) // Mode configures the Sniffer validation mode. @@ -99,7 +98,7 @@ func Sniff(ctx context.Context, name string, target *check.Pod, } return nil, fmt.Errorf("Failed to execute tcpdump: %w", err) - case <-inctimer.After(100 * time.Millisecond): + case <-time.After(100 * time.Millisecond): line, err := sniffer.stdout.ReadString('\n') if err != nil && !errors.Is(err, io.EOF) { return nil, fmt.Errorf("Failed to read kubectl exec's stdout: %w", err) diff --git a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/tests/encryption.go b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/tests/encryption.go index 819f2e0e40..f42a4c0fba 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/tests/encryption.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/tests/encryption.go @@ -6,12 +6,18 @@ package tests import ( "context" "fmt" + "maps" + "slices" "strings" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/sets" + "github.com/cilium/cilium/cilium-cli/connectivity/check" "github.com/cilium/cilium/cilium-cli/connectivity/sniff" "github.com/cilium/cilium/cilium-cli/utils/features" "github.com/cilium/cilium/pkg/defaults" + ciliumv2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" "github.com/cilium/cilium/pkg/versioncheck" ) @@ -197,6 +203,26 @@ func isWgEncap(t *check.Test) bool { return true } +// checkIPSecPodToPod checks whether in case of IPSec being enabled and used +// during the podToPodEncryption test it should be executed or skipped due to: +// +// 1. missing backporting of the commit in previous versions; +// 2. usage of IPv6, for which the test is flaky (see https://github.com/cilium/cilium/issues/35485). +// +// Once the above reasons are fixed, this function can be removed. +func checkIPSecPodToPod(t *check.Test, ipFam features.IPFamily) error { + if e, ok := t.Context().Feature(features.EncryptionPod); !(ok && e.Enabled && e.Mode == "ipsec") { + return nil + } + if !versioncheck.MustCompile(">=1.17.0")(t.Context().CiliumVersion) { + return fmt.Errorf("enabling test for IPSec requires backporting") + } + if ipFam == features.IPFamilyV6 { + return fmt.Errorf("inactive IPv6 test with IPSec, see https://github.com/cilium/cilium/issues/35485") + } + return nil +} + // PodToPodEncryption is a test case which checks the following: // - There is a connectivity between pods on different nodes when any // encryption mode is on (either WireGuard or IPsec). @@ -246,6 +272,10 @@ func (s *podToPodEncryption) Run(ctx context.Context, t *check.Test) { } t.ForEachIPFamily(func(ipFam features.IPFamily) { + if err := checkIPSecPodToPod(t, ipFam); err != nil { + t.Debugf("Skipping test: %v", err) + return + } testNoTrafficLeak(ctx, t, s, client, &server, &clientHost, &serverHost, requestHTTP, ipFam, assertNoLeaks, true, wgEncap) }) } @@ -300,6 +330,65 @@ func testNoTrafficLeak(ctx context.Context, t *check.Test, s check.Scenario, } } +func nodeToNodeEncTestPods(nodes map[check.NodeIdentity]*ciliumv2.CiliumNode, excludeSelector labels.Selector, clients, servers []check.Pod) (client, server *check.Pod) { + nodeKey := func(pod *check.Pod) check.NodeIdentity { + if pod != nil { + return check.NodeIdentity{Cluster: pod.K8sClient.ClusterName(), Name: pod.NodeName()} + } + return check.NodeIdentity{} + } + + acceptableNodes := func(pods []check.Pod) sets.Set[check.NodeIdentity] { + keys := sets.New[check.NodeIdentity]() + for _, pod := range pods { + node := nodes[nodeKey(&pod)] + if node == nil { + continue + } + + if excludeSelector.Matches(labels.Set(node.Labels)) { + continue + } + + keys.Insert(nodeKey(&pod)) + } + return keys + } + + getRandomPod := func(pods []check.Pod, nodes sets.Set[check.NodeIdentity]) *check.Pod { + for _, pod := range pods { + if nodes.Has(nodeKey(&pod)) { + return &pod + } + } + + return nil + } + + clientNodes := acceptableNodes(clients) + serverNodes := acceptableNodes(servers) + + // Prefer selecting a client (server) running on a node which does not + // host a server (client) as well, to maximize the possibilities of finding + // a valid combination. + clientNodesOnly := clientNodes.Difference(serverNodes) + serverNodesOnly := serverNodes.Difference(clientNodes) + + client = getRandomPod(clients, clientNodesOnly) + if client == nil { + client = getRandomPod(clients, clientNodes) + } + + server = getRandomPod(servers, serverNodesOnly) + if server == nil { + // Make sure to not pick a server hosted on the same node of the client. + serverNodes.Delete(nodeKey(client)) + server = getRandomPod(servers, serverNodes) + } + + return client, server +} + func NodeToNodeEncryption(reqs ...features.Requirement) check.Scenario { return &nodeToNodeEncryption{reqs} } @@ -311,17 +400,28 @@ func (s *nodeToNodeEncryption) Name() string { } func (s *nodeToNodeEncryption) Run(ctx context.Context, t *check.Test) { - client := t.Context().RandomClientPod() - - var server check.Pod - for _, pod := range t.Context().EchoPods() { - // Make sure that the server pod is on another node than client - if pod.Pod.Status.HostIP != client.Pod.Status.HostIP { - server = pod - break + ct := t.Context() + encryptNode, _ := ct.Feature(features.EncryptionNode) + + // Node to node encryption can be disabled on specific nodes (e.g., + // control plane ones) to prevent e.g., losing connectivity to the + // Kubernetes API Server. Let's take that into account when selecting + // the target pods/nodes. + excludeNodes := labels.Nothing() + if encryptNode.Enabled { + var err error + if excludeNodes, err = labels.Parse(encryptNode.Mode); err != nil { + t.Fatalf("unable to parse label selector %s: %s", encryptNode.Mode, err) } } + client, server := nodeToNodeEncTestPods(ct.CiliumNodes(), excludeNodes, + slices.Collect(maps.Values(ct.ClientPods())), + slices.Collect(maps.Values(ct.EchoPods()))) + if client == nil || server == nil { + t.Fatal("Could not find matching pods: is node to node encryption disabled on all nodes hosting test pods?") + } + // clientHost is a pod running on the same node as the client pod, just in // the host netns. clientHost := t.Context().HostNetNSPodsByNode()[client.Pod.Spec.NodeName] @@ -362,6 +462,6 @@ func (s *nodeToNodeEncryption) Run(ctx context.Context, t *check.Test) { if onlyPodToPodWGWithTunnel { hostToPodAssertNoLeaks = true } - testNoTrafficLeak(ctx, t, s, &clientHost, &server, &clientHost, &serverHost, requestHTTP, ipFam, hostToPodAssertNoLeaks, false, wgEncap) + testNoTrafficLeak(ctx, t, s, &clientHost, server, &clientHost, &serverHost, requestHTTP, ipFam, hostToPodAssertNoLeaks, false, wgEncap) }) } diff --git a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/tests/health.go b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/tests/health.go index 3de33302e6..058ed288d4 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/tests/health.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/tests/health.go @@ -17,7 +17,6 @@ import ( "github.com/cilium/cilium/cilium-cli/connectivity/check" "github.com/cilium/cilium/cilium-cli/defaults" - "github.com/cilium/cilium/pkg/inctimer" ) func CiliumHealth() check.Scenario { @@ -44,8 +43,6 @@ func runHealthProbe(ctx context.Context, t *check.Test, pod *check.Pod) { // Probe health status until it passes checks or timeout is reached. for { - retryTimer := inctimer.After(time.Second) - if _, err := pod.K8sClient.GetPod(ctx, pod.Pod.Namespace, pod.Pod.Name, metav1.GetOptions{}); k8serrors.IsNotFound(err) { t.Failf("cilium-health validation failed. Cilium Agent Pod %s/%s no longer exists", pod.Pod.Namespace, pod.Pod.Name) return @@ -66,7 +63,7 @@ func runHealthProbe(ctx context.Context, t *check.Test, pod *check.Pod) { case <-done: t.Context().Fatalf("cilium-health probe on '%s' failed: %s", pod.Name(), err) return - case <-retryTimer: + case <-time.After(time.Second): } } } diff --git a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/tests/multicast.go b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/tests/multicast.go new file mode 100644 index 0000000000..76a377f2c0 --- /dev/null +++ b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/tests/multicast.go @@ -0,0 +1,363 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Cilium + +package tests + +import ( + "context" + "errors" + "fmt" + "net/netip" + "strings" + "sync" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/cilium/ebpf" + + v2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" + "github.com/cilium/cilium/pkg/lock" + "github.com/cilium/cilium/pkg/node/addressing" + + "github.com/cilium/cilium/cilium-cli/connectivity/check" + "github.com/cilium/cilium/cilium-cli/defaults" + "github.com/cilium/cilium/cilium-cli/utils/features" +) + +const ( + testMulticastGroupIP = "239.255.9.9" + testSocatPort = 6666 +) + +// Having data to restore group and subscriber status after testing +var NodeWithoutGroup []string +var NotSubscribePodAddress map[string][]v2.NodeAddress +var ipToPodMap lock.Map[v2.NodeAddress, string] +var NodeWithoutGroupMu lock.RWMutex +var NotSubscribePodAddressMu lock.RWMutex + +type socatMulticast struct { +} + +func SocatMulticast() check.Scenario { + return &socatMulticast{} +} + +func (s *socatMulticast) Name() string { + return "multicast" +} + +func (s *socatMulticast) Run(ctx context.Context, t *check.Test) { + ct := t.Context() + defer func() { + s.cleanup(ctx, t) + }() + NotSubscribePodAddress = make(map[string][]v2.NodeAddress) + + // Add all cilium nodes to the multicast group + if err := s.addAllNodes(ctx, t); err != nil { + t.Fatalf("Fatal error occurred while adding all cilium nodes to multicast group: %v", err) + } + + bgCtx, cancelBg := context.WithCancel(ctx) + defer cancelBg() + + var wg sync.WaitGroup + + // Sender: Start repeated socat multicast client in the background) + for _, clientPod := range ct.SocatClientPods() { + wg.Add(1) + go func(pod check.Pod) { + defer wg.Done() + cmd := ct.SocatClientCommand(testSocatPort, testMulticastGroupIP) + doneCh := make(chan struct{}) + go func() { + _, stdErr, err := pod.K8sClient.ExecInPodWithStderr(bgCtx, pod.Pod.Namespace, pod.Pod.Name, pod.Pod.Labels["name"], cmd) + if err != nil && !strings.Contains(err.Error(), "context canceled") { + errMsg := fmt.Sprintf("Error: %v, Stderr: %s", err, stdErr.String()) + t.Logf("Error in background task for pod %s: %v", pod.Name(), errMsg) + } + close(doneCh) + }() + select { + case <-doneCh: + // Task finished normally + case <-bgCtx.Done(): + // Context was cancelled, handle cleanup + cancelCmd := ct.KillMulticastTestSender() + _, _, err := pod.K8sClient.ExecInPodWithStderr(ctx, pod.Pod.Namespace, pod.Pod.Name, pod.Pod.Labels["name"], cancelCmd) + if err != nil { + t.Logf("Error cancelling command for pod %s: %v", pod.Name(), err) + } + } + }(clientPod) + } + + // Receiver: Execute socat multicast server and check if multicast packets are coming in. + for _, socatServerPod := range ct.SocatServerPods() { + t.NewAction(s, "socat multicast", &socatServerPod, nil, features.IPFamilyV4).Run(func(a *check.Action) { + cmd := ct.SocatServer1secCommand(socatServerPod, testSocatPort, testMulticastGroupIP) + // The exit code of socat server command with timeout is 0 if a packet is received, + // and 124 if no packet is received. + a.ExecInPod(ctx, cmd) + }) + } + + cancelBg() + wg.Wait() +} + +// Restore the state of the multicast group and subscriber after the test +func (s *socatMulticast) cleanup(ctx context.Context, t *check.Test) { + ct := t.Context() + client := ct.K8sClient() + ciliumNodesList, err := client.ListCiliumNodes(ctx) + if err != nil { + t.Fatalf("Fatal error occurred while listing cilium nodes: %v", err) + } + ciliumNodes := ciliumNodesList.Items + for _, ciliumNode := range ciliumNodes { + if s.isNodeWithoutGroup(ciliumNode.Name) { + if err := s.delGroup(ctx, t, ciliumNode.Name); err != nil { + t.Fatalf("Fatal error occurred while deleting multicast group: %v", err) + } + } else { + for _, podAddress := range NotSubscribePodAddress[ciliumNode.Name] { + if s.isNotSubscribePodAddress(ciliumNode.Name, podAddress) { + if err := s.delSubscriber(ctx, t, ciliumNode.Name, podAddress.IP); err != nil { + t.Fatalf("Fatal error occurred while deleting subscriber: %v", err) + } + } + } + } + } +} + +func (s *socatMulticast) addNodeWithoutGroup(nodeName string) { + NodeWithoutGroupMu.Lock() + defer NodeWithoutGroupMu.Unlock() + NodeWithoutGroup = append(NodeWithoutGroup, nodeName) +} + +func (s *socatMulticast) isNodeWithoutGroup(nodeName string) bool { + NodeWithoutGroupMu.RLock() + defer NodeWithoutGroupMu.RUnlock() + for _, node := range NodeWithoutGroup { + if node == nodeName { + return true + } + } + return false +} + +func (s *socatMulticast) addNotSubscribePodAddress(nodeName string, podAddress v2.NodeAddress) { + NotSubscribePodAddressMu.Lock() + defer NotSubscribePodAddressMu.Unlock() + NotSubscribePodAddress[nodeName] = append(NotSubscribePodAddress[nodeName], podAddress) +} + +func (s *socatMulticast) isNotSubscribePodAddress(nodeName string, podAddress v2.NodeAddress) bool { + NotSubscribePodAddressMu.RLock() + defer NotSubscribePodAddressMu.RUnlock() + for _, address := range NotSubscribePodAddress[nodeName] { + if address.IP == podAddress.IP { + return true + } + } + return false +} + +func (s *socatMulticast) getCiliumNode(ctx context.Context, t *check.Test, nodeName string) (v2.CiliumNode, error) { + ct := t.Context() + client := ct.K8sClient() + ciliumNodes, err := client.ListCiliumNodes(ctx) + if err != nil { + return v2.CiliumNode{}, err + } + var ciliumNode v2.CiliumNode + for _, node := range ciliumNodes.Items { + if node.Name == nodeName { + ciliumNode = node + } + } + return ciliumNode, nil +} + +func (s *socatMulticast) getCiliumInternalIP(ctx context.Context, t *check.Test, nodeName string) (v2.NodeAddress, error) { + ciliumNode, err := s.getCiliumNode(ctx, t, nodeName) + if err != nil { + return v2.NodeAddress{}, fmt.Errorf("unable to get cilium node: %w", err) + } + addrs := ciliumNode.Spec.Addresses + var ciliumInternalIP v2.NodeAddress + for _, addr := range addrs { + if addr.AddrType() == addressing.NodeCiliumInternalIP { + ip, err := netip.ParseAddr(addr.IP) + if err != nil { + continue + } + if ip.Is4() { + ciliumInternalIP = addr + } + } + } + if ciliumInternalIP.IP == "" { + return v2.NodeAddress{}, fmt.Errorf("ciliumInternalIP not found") + } + return ciliumInternalIP, nil +} + +// To record the correspondence between CiliumInternalIp and cilium-agent +func (s *socatMulticast) populateMaps(ctx context.Context, t *check.Test, ciliumPods []corev1.Pod) error { + var wg sync.WaitGroup + errCh := make(chan error, len(ciliumPods)) + wg.Add(len(ciliumPods)) + + for _, ciliumPod := range ciliumPods { + go func(pod corev1.Pod) { + defer wg.Done() + ciliumInternalIP, err := s.getCiliumInternalIP(ctx, t, pod.Spec.NodeName) + if err != nil { + errCh <- err + return + } + ipToPodMap.Store(ciliumInternalIP, pod.Name) + }(ciliumPod) + } + + wg.Wait() + close(errCh) + + var errRet error + for fetchData := range errCh { + errRet = errors.Join(errRet, fetchData) + } + return errRet +} + +// create multicast group and add all cilium nodes to the multicast group for testing +func (s *socatMulticast) addAllNodes(ctx context.Context, t *check.Test) error { + ct := t.Context() + client := ct.K8sClient() + + ciliumPodsList, err := client.ListPods(ctx, ct.Params().CiliumNamespace, metav1.ListOptions{LabelSelector: defaults.AgentPodSelector}) + if err != nil { + return err + } + ciliumPods := ciliumPodsList.Items + + // Create a map of ciliumInternalIPs of all nodes + if err := s.populateMaps(ctx, t, ciliumPods); err != nil { + return err + } + + var wg sync.WaitGroup + errCh := make(chan error, len(ciliumPods)) + wg.Add(len(ciliumPods)) + + for _, ciliumPod := range ciliumPods { + go func(pod corev1.Pod) { + defer wg.Done() + // If there are not specified multicast group, create it + cmd := []string{"cilium-dbg", "bpf", "multicast", "subscriber", "list", testMulticastGroupIP} + _, stdErr, err := client.ExecInPodWithStderr(ctx, pod.Namespace, pod.Name, defaults.AgentContainerName, cmd) + if err != nil { + if !strings.Contains(stdErr.String(), ebpf.ErrKeyNotExist.Error()) { + errMsg := fmt.Sprintf("Error: %v, Stderr: %s", err, stdErr.String()) + errCh <- errors.New(errMsg) + t.Fatalf("Fatal error occurred while checking multicast group %s in %s", testMulticastGroupIP, pod.Spec.NodeName) + return + } + s.addNodeWithoutGroup(pod.Spec.NodeName) + cmd = []string{"cilium-dbg", "bpf", "multicast", "group", "add", testMulticastGroupIP} + _, stdErr, err := client.ExecInPodWithStderr(ctx, pod.Namespace, pod.Name, defaults.AgentContainerName, cmd) + if err != nil { + errMsg := fmt.Sprintf("Error: %v, Stderr: %s", err, stdErr.String()) + errCh <- errors.New(errMsg) + t.Fatalf("Fatal error occurred while creating multicast group %s in %s", testMulticastGroupIP, pod.Spec.NodeName) + return + } + } + // Add all ciliumInternalIPs of all nodes to the multicast group as subscribers + ipToPodMap.Range(func(ip v2.NodeAddress, podName string) bool { + if ip.IP != "" && pod.Name != podName { // My node itself does not need to be in a multicast group. + cmd = []string{"cilium-dbg", "bpf", "multicast", "subscriber", "add", testMulticastGroupIP, ip.IP} + _, stdErr, err := client.ExecInPodWithStderr(ctx, pod.Namespace, pod.Name, defaults.AgentContainerName, cmd) + if err == nil { + s.addNotSubscribePodAddress(pod.Spec.NodeName, ip) + } else if !strings.Contains(stdErr.String(), ebpf.ErrKeyExist.Error()) { + errMsg := fmt.Sprintf("Error: %v, Stderr: %s", err, stdErr.String()) + errCh <- errors.New(errMsg) + t.Fatalf("Fatal error occurred while adding node %s to multicast group %s in %s", ip.IP, testMulticastGroupIP, pod.Spec.NodeName) + return false // Stop iteration + } + } + return true // Continue iteration + }) + }(ciliumPod) + } + + wg.Wait() + close(errCh) + + var errRet error + for fetchData := range errCh { + errRet = errors.Join(errRet, fetchData) + } + return errRet +} + +// Delete multicast group in designated node +func (s *socatMulticast) delGroup(ctx context.Context, t *check.Test, nodeName string) error { + ct := t.Context() + client := ct.K8sClient() + + ciliumPodsList, err := client.ListPods(ctx, ct.Params().CiliumNamespace, metav1.ListOptions{LabelSelector: defaults.AgentPodSelector}) + if err != nil { + return err + } + ciliumPods := ciliumPodsList.Items + + for _, ciliumPod := range ciliumPods { + if nodeName == ciliumPod.Spec.NodeName { + cmd := []string{"cilium-dbg", "bpf", "multicast", "group", "delete", testMulticastGroupIP} + _, stdErr, err := client.ExecInPodWithStderr(ctx, ciliumPod.Namespace, ciliumPod.Name, defaults.AgentContainerName, cmd) + if err != nil { + if !strings.Contains(stdErr.String(), ebpf.ErrKeyNotExist.Error()) { + errMsg := fmt.Sprintf("Error: %v while deleting Multicast Group for test %s, Stderr: %s", err, testMulticastGroupIP, stdErr.String()) + return errors.New(errMsg) + } + } + break + } + } + return nil +} + +// Delete designated subscriber in designated node +func (s *socatMulticast) delSubscriber(ctx context.Context, t *check.Test, nodeName string, subscriberIP string) error { + ct := t.Context() + client := ct.K8sClient() + + ciliumPodsList, err := client.ListPods(ctx, ct.Params().CiliumNamespace, metav1.ListOptions{LabelSelector: defaults.AgentPodSelector}) + if err != nil { + return err + } + ciliumPods := ciliumPodsList.Items + + for _, ciliumPod := range ciliumPods { + if nodeName == ciliumPod.Spec.NodeName { + cmd := []string{"cilium-dbg", "bpf", "multicast", "subscriber", "delete", testMulticastGroupIP, subscriberIP} + _, stdErr, err := client.ExecInPodWithStderr(ctx, ciliumPod.Namespace, ciliumPod.Name, defaults.AgentContainerName, cmd) + if err != nil { + if !strings.Contains(stdErr.String(), ebpf.ErrKeyNotExist.Error()) { + errMsg := fmt.Sprintf("Error: %v while removing %s from Multicast Group %s Stderr: %s", err, subscriberIP, testMulticastGroupIP, stdErr.String()) + return errors.New(errMsg) + } + } + break + } + } + return nil +} diff --git a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/tests/pod.go b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/tests/pod.go index 04cc8eabd3..54c6c1a4e4 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/tests/pod.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/tests/pod.go @@ -6,10 +6,12 @@ package tests import ( "context" "fmt" + "regexp" "strconv" "strings" "github.com/cilium/cilium/cilium-cli/connectivity/check" + "github.com/cilium/cilium/cilium-cli/defaults" "github.com/cilium/cilium/cilium-cli/utils/features" ) @@ -253,3 +255,97 @@ func (s *podToPodNoFrag) Run(ctx context.Context, t *check.Test) { }) } + +func PodToPodMissingIPCache(opts ...Option) check.Scenario { + options := &labelsOption{} + for _, opt := range opts { + opt(options) + } + return &podToPodMissingIPCache{ + sourceLabels: options.sourceLabels, + destinationLabels: options.destinationLabels, + method: options.method, + } +} + +type podToPodMissingIPCache struct { + sourceLabels map[string]string + destinationLabels map[string]string + method string +} + +func (s *podToPodMissingIPCache) Name() string { + return "pod-to-pod-missing-ipcache" +} + +func (s *podToPodMissingIPCache) Run(ctx context.Context, t *check.Test) { + var i int + ct := t.Context() + + // Temporarily delete echo pods entries from ipcache + ipcacheGetPat := regexp.MustCompile(`identity=(\d+)\s+encryptkey=(\d+)\s+tunnelendpoint=([\d\.]+)`) + for _, echo := range ct.EchoPods() { + echoIP := echo.Address(features.IPFamilyV4) + for _, ciliumPod := range ct.CiliumPods() { + lookupCmd := []string{"cilium", "bpf", "ipcache", "get", echoIP} + output, err := ciliumPod.K8sClient.ExecInPod(ctx, ciliumPod.Pod.Namespace, ciliumPod.Pod.Name, defaults.AgentContainerName, lookupCmd) + if err != nil { + ct.Warnf(`failed to lookup IP cache entry: "%s", %v, "%s"`, lookupCmd, err, output.String()) + continue + } + matches := ipcacheGetPat.FindStringSubmatch(output.String()) + identity := matches[1] + encryptkey := matches[2] + tunnelendpoint := matches[3] + + deleteCmd := []string{"cilium", "bpf", "ipcache", "delete", echoIP + "/32"} + if output, err = ciliumPod.K8sClient.ExecInPod(ctx, ciliumPod.Pod.Namespace, ciliumPod.Pod.Name, defaults.AgentContainerName, deleteCmd); err != nil { + ct.Warnf(`failed to delete IP cache entry: "%s", %v, "%s"`, deleteCmd, err, output.String()) + continue + } + + updateCmd := []string{"cilium", "bpf", "ipcache", "update", echoIP + "/32"} + updateCmd = append(updateCmd, "--tunnelendpoint", tunnelendpoint, "--identity", identity, "--encryptkey", encryptkey) + defer func(ciliumPod check.Pod, updateCmd []string) { + output, err := ciliumPod.K8sClient.ExecInPod(ctx, ciliumPod.Pod.Namespace, ciliumPod.Pod.Name, defaults.AgentContainerName, updateCmd) + if err != nil { + ct.Warnf(`failed to restore IP cache entry: "%s", %v, "%s"`, updateCmd, err, output.String()) + } + }(ciliumPod, updateCmd) + } + } + + for _, client := range ct.ClientPods() { + if !hasAllLabels(client, s.sourceLabels) { + continue + } + for _, echo := range ct.EchoPods() { + if !hasAllLabels(echo, s.destinationLabels) { + continue + } + + // Skip if echo pod is on the same node as client + if echo.Pod.Spec.NodeName == client.Pod.Spec.NodeName { + continue + } + + t.ForEachIPFamily(func(ipFam features.IPFamily) { + if ipFam == features.IPFamilyV6 { + // encryption-strict-mode-cidr only accepts an IPv4 CIDR + return + } + t.NewAction(s, fmt.Sprintf("curl-%s-%d", ipFam, i), &client, echo, ipFam).Run(func(a *check.Action) { + a.ExecInPod(ctx, ct.CurlCommand(echo, ipFam)) + + a.ValidateFlows(ctx, client, a.GetEgressRequirements(check.FlowParameters{})) + a.ValidateFlows(ctx, echo, a.GetIngressRequirements(check.FlowParameters{})) + + a.ValidateMetrics(ctx, echo, a.GetIngressMetricsRequirements()) + a.ValidateMetrics(ctx, echo, a.GetEgressMetricsRequirements()) + }) + }) + + i++ + } + } +} diff --git a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/tests/service.go b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/tests/service.go index ee516629e5..74ffa2fd8f 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/connectivity/tests/service.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/connectivity/tests/service.go @@ -12,7 +12,6 @@ import ( "github.com/cilium/cilium/cilium-cli/connectivity/check" "github.com/cilium/cilium/cilium-cli/utils/features" - "github.com/cilium/cilium/pkg/versioncheck" ) // PodToService sends an HTTP request from all client Pods @@ -228,13 +227,6 @@ func curlNodePort(ctx context.Context, s check.Scenario, t *check.Test, } } - // Skip IPv6 requests when running on <1.14.0 Cilium with CNPs - if features.GetIPFamily(addr.Address) == features.IPFamilyV6 && - versioncheck.MustCompile("<1.14.0")(t.Context().CiliumVersion) && - (len(t.CiliumNetworkPolicies()) > 0 || len(t.KubernetesNetworkPolicies()) > 0) { - continue - } - // Manually construct an HTTP endpoint to override the destination IP // and port of the request. ep := check.HTTPEndpoint(name, fmt.Sprintf("%s://%s:%d%s", svc.Scheme(), addr.Address, np, svc.Path())) diff --git a/vendor/github.com/cilium/cilium/cilium-cli/defaults/defaults.go b/vendor/github.com/cilium/cilium/cilium-cli/defaults/defaults.go index c596c70eb4..4248db619e 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/defaults/defaults.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/defaults/defaults.go @@ -76,6 +76,8 @@ const ( ConnectivityTestConnDisruptImage = "quay.io/cilium/test-connection-disruption:v0.0.14@sha256:c3fd56e326ae16f6cb63dbb2e26b4e47ec07a123040623e11399a7fe1196baa0" // renovate: datasource=docker ConnectivityTestFRRImage = "quay.io/frrouting/frr:10.1.1@sha256:7c7901eb5611f12634395c949e59663e154b37cf006f32c7f4c8650884cdc0b1" + // renovate: datasource=docker + ConnectivityTestSocatImage = "docker.io/alpine/socat:1.8.0.0@sha256:a6be4c0262b339c53ddad723cdd178a1a13271e1137c65e27f90a08c16de02b8" ConfigMapName = "cilium-config" @@ -87,7 +89,7 @@ const ( FlowWaitTimeout = 10 * time.Second FlowRetryInterval = 500 * time.Millisecond - PolicyWaitTimeout = 15 * time.Second + PolicyWaitTimeout = 30 * time.Second ConnectRetry = 3 ConnectRetryDelay = 3 * time.Second @@ -158,6 +160,7 @@ var ( "Host datapath not ready", "Unknown ICMPv4 code", "Forbidden ICMPv6 message", + "No egress gateway found", } ExpectedXFRMErrors = []string{ diff --git a/vendor/github.com/cilium/cilium/cilium-cli/hubble/hubble.go b/vendor/github.com/cilium/cilium/cilium-cli/hubble/hubble.go index 911b0f688a..6b5cd93e7e 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/hubble/hubble.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/hubble/hubble.go @@ -9,18 +9,12 @@ import ( "io" "helm.sh/helm/v3/pkg/cli/values" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/cilium/cilium/cilium-cli/defaults" "github.com/cilium/cilium/cilium-cli/internal/helm" "github.com/cilium/cilium/cilium-cli/k8s" ) -type k8sHubbleImplementation interface { - GetService(ctx context.Context, namespace, name string, opts metav1.GetOptions) (*corev1.Service, error) -} - type Parameters struct { Namespace string Relay bool @@ -28,7 +22,6 @@ type Parameters struct { UI bool UIPortForward int Writer io.Writer - Context string // Only for 'kubectl' pass-through commands // UIOpenBrowser will automatically open browser if true UIOpenBrowser bool diff --git a/vendor/github.com/cilium/cilium/cilium-cli/hubble/relay.go b/vendor/github.com/cilium/cilium/cilium-cli/hubble/relay.go index 137716f04d..0de0af2edf 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/hubble/relay.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/hubble/relay.go @@ -7,28 +7,16 @@ import ( "context" "fmt" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/cilium/cilium/cilium-cli/internal/utils" + "github.com/cilium/cilium/cilium-cli/k8s" ) -func (p *Parameters) RelayPortForwardCommand(ctx context.Context, client k8sHubbleImplementation) error { - relaySvc, err := client.GetService(ctx, p.Namespace, "hubble-relay", metav1.GetOptions{}) +func (p *Parameters) RelayPortForwardCommand(ctx context.Context, k8sClient *k8s.Client) error { + // default to first port configured on the service when svcPort is set to 0 + res, err := k8sClient.PortForwardService(ctx, p.Namespace, "hubble-relay", int32(p.PortForward), 0) if err != nil { - return err + return fmt.Errorf("failed to port forward: %w", err) } - - args := []string{ - "port-forward", - "-n", p.Namespace, - "svc/hubble-relay", - "--address", "127.0.0.1", - fmt.Sprintf("%d:%d", p.PortForward, relaySvc.Spec.Ports[0].Port)} - - if p.Context != "" { - args = append([]string{"--context", p.Context}, args...) - } - - _, err = utils.Exec(p, "kubectl", args...) - return err + p.Log("ℹ️ Hubble Relay is available at 127.0.0.1:%d", res.ForwardedPort.Local) + <-ctx.Done() + return nil } diff --git a/vendor/github.com/cilium/cilium/cilium-cli/hubble/ui.go b/vendor/github.com/cilium/cilium/cilium-cli/hubble/ui.go index 3556d43eff..41b8133e2f 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/hubble/ui.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/hubble/ui.go @@ -4,42 +4,33 @@ package hubble import ( + "context" "fmt" "io" - "time" "github.com/pkg/browser" - "github.com/cilium/cilium/cilium-cli/internal/utils" + "github.com/cilium/cilium/cilium-cli/k8s" ) -func (p *Parameters) UIPortForwardCommand() error { - args := []string{ - "port-forward", - "-n", p.Namespace, - "svc/hubble-ui", - "--address", "127.0.0.1", - fmt.Sprintf("%d:80", p.UIPortForward)} - - if p.Context != "" { - args = append([]string{"--context", p.Context}, args...) +func (p *Parameters) UIPortForwardCommand(ctx context.Context, k8sClient *k8s.Client) error { + // default to first port configured on the service when svcPort is set to 0 + res, err := k8sClient.PortForwardService(ctx, p.Namespace, "hubble-ui", int32(p.UIPortForward), 0) + if err != nil { + return fmt.Errorf("failed to port forward: %w", err) } - go func() { - time.Sleep(5 * time.Second) - url := fmt.Sprintf("http://localhost:%d", p.UIPortForward) - - if p.UIOpenBrowser { - // avoid cluttering stdout/stderr when opening the browser - browser.Stdout = io.Discard - browser.Stderr = io.Discard - p.Log("ℹ️ Opening %q in your browser...", url) - browser.OpenURL(url) - } else { - p.Log("ℹ️ Hubble UI is available at %q", url) - } - }() + url := fmt.Sprintf("http://localhost:%d", res.ForwardedPort.Local) + if p.UIOpenBrowser { + // avoid cluttering stdout/stderr when opening the browser + browser.Stdout = io.Discard + browser.Stderr = io.Discard + p.Log("ℹ️ Opening %q in your browser...", url) + browser.OpenURL(url) + } else { + p.Log("ℹ️ Hubble UI is available at %q", url) + } - _, err := utils.Exec(p, "kubectl", args...) - return err + <-ctx.Done() + return nil } diff --git a/vendor/github.com/cilium/cilium/cilium-cli/k8s/client.go b/vendor/github.com/cilium/cilium/cilium-cli/k8s/client.go index abc9604bed..c44296e3c8 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/k8s/client.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/k8s/client.go @@ -23,6 +23,7 @@ import ( appsv1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" + discoveryv1 "k8s.io/api/discovery/v1" networkingv1 "k8s.io/api/networking/v1" rbacv1 "k8s.io/api/rbac/v1" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" @@ -39,7 +40,7 @@ import ( "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" - _ "k8s.io/client-go/plugin/pkg/client/auth" // Register all auth providers (azure, gcp, oidc, openstack, ..). + _ "k8s.io/client-go/plugin/pkg/client/auth" // Register all auth providers (azure, gcp, oidc, openstack, ..) "k8s.io/client-go/rest" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" "k8s.io/client-go/transport/spdy" @@ -53,6 +54,12 @@ import ( "github.com/cilium/cilium/pkg/versioncheck" ) +func init() { + // Register the Cilium types in the default scheme. + _ = ciliumv2.AddToScheme(scheme.Scheme) + _ = ciliumv2alpha1.AddToScheme(scheme.Scheme) +} + type Client struct { Clientset kubernetes.Interface ExtensionClientset apiextensionsclientset.Interface // k8s api extension needed to retrieve CRDs @@ -66,10 +73,6 @@ type Client struct { } func NewClient(contextName, kubeconfig, ciliumNamespace string) (*Client, error) { - // Register the Cilium types in the default scheme. - _ = ciliumv2.AddToScheme(scheme.Scheme) - _ = ciliumv2alpha1.AddToScheme(scheme.Scheme) - restClientGetter := genericclioptions.ConfigFlags{ Context: &contextName, KubeConfig: &kubeconfig, @@ -311,11 +314,12 @@ func (c *Client) PodLogs(namespace, name string, opts *corev1.PodLogOptions) *re return c.Clientset.CoreV1().Pods(namespace).GetLogs(name, opts) } -func (c *Client) CiliumLogs(ctx context.Context, namespace, pod string, since time.Time) (string, error) { +func (c *Client) CiliumLogs(ctx context.Context, namespace, pod string, since time.Time, previous bool) (string, error) { opts := &corev1.PodLogOptions{ Container: defaults.AgentContainerName, Timestamps: true, SinceTime: &metav1.Time{Time: since}, + Previous: previous, } req := c.PodLogs(namespace, pod, opts) podLogs, err := req.Stream(ctx) @@ -353,8 +357,9 @@ func (c *Client) ExecInPod(ctx context.Context, namespace, pod, container string Container: container, Command: command, }) + if err != nil { - return result.Stdout, err + return result.Stdout, fmt.Errorf("%w: %q", err, result.Stderr.String()) } if errString := result.Stderr.String(); errString != "" { @@ -899,6 +904,10 @@ func (c *Client) ListEndpoints(ctx context.Context, o metav1.ListOptions) (*core return c.Clientset.CoreV1().Endpoints(corev1.NamespaceAll).List(ctx, o) } +func (c *Client) ListEndpointSlices(ctx context.Context, o metav1.ListOptions) (*discoveryv1.EndpointSliceList, error) { + return c.Clientset.DiscoveryV1().EndpointSlices(corev1.NamespaceAll).List(ctx, o) +} + func (c *Client) ListIngressClasses(ctx context.Context, o metav1.ListOptions) (*networkingv1.IngressClassList, error) { return c.Clientset.NetworkingV1().IngressClasses().List(ctx, o) } @@ -1094,3 +1103,64 @@ func (c *Client) CreateEphemeralContainer(ctx context.Context, pod *corev1.Pod, ctx, pod.Name, types.StrategicMergePatchType, patch, metav1.PatchOptions{}, "ephemeralcontainers", ) } + +type Object interface { + metav1.Object + runtime.Object +} + +// ApplyGeneric uses server-side apply to merge changes to an arbitrary object. +// Returns the applied object. +func (c *Client) ApplyGeneric(ctx context.Context, obj Object) (*unstructured.Unstructured, error) { + gvk, resource, err := c.Describe(obj) + if err != nil { + return nil, fmt.Errorf("could not get Kubernetes API information for %s/%s: %w", obj.GetNamespace(), obj.GetName(), err) + } + + // Now, convert the object to an Unstructured + u, ok := obj.(*unstructured.Unstructured) + if !ok { + b, err := json.Marshal(obj) + if err != nil { + return nil, fmt.Errorf("failed to convert to unstructured (marshal): %w", err) + } + u = &unstructured.Unstructured{} + if err := json.Unmarshal(b, u); err != nil { + return nil, fmt.Errorf("failed to convert to unstructured (unmarshal): %w", err) + } + } + + // Dragons: If we're passed a non-Unstructured object (e.g. v1.ConfigMap), it won't have + // the GVK set necessarily. So, use the retrieved GVK from the schema and add it. + // This is a no-op for Unstructured objects. + // TODO: use a proper codec + serializer + u.GetObjectKind().SetGroupVersionKind(gvk) + + // clear ManagedFields; it is not allowed to specify them in a Patch + u.SetManagedFields(nil) + + dynamicClient := c.DynamicClientset.Resource(resource).Namespace(obj.GetNamespace()) + return dynamicClient.Apply(ctx, obj.GetName(), u, metav1.ApplyOptions{Force: true, FieldManager: "cilium-cli"}) +} + +func (c *Client) GetGeneric(ctx context.Context, namespace, name string, obj Object) (*unstructured.Unstructured, error) { + _, resource, err := c.Describe(obj) + if err != nil { + return nil, fmt.Errorf("could not get Kubernetes API information for %s/%s: %w", obj.GetNamespace(), obj.GetName(), err) + } + + dynamicClient := c.DynamicClientset.Resource(resource).Namespace(namespace) + + return dynamicClient.Get(ctx, name, metav1.GetOptions{}) +} + +func (c *Client) DeleteGeneric(ctx context.Context, obj Object) error { + _, resource, err := c.Describe(obj) + if err != nil { + return fmt.Errorf("could not get Kubernetes API information for %s/%s: %w", obj.GetNamespace(), obj.GetName(), err) + } + + dynamicClient := c.DynamicClientset.Resource(resource).Namespace(obj.GetNamespace()) + + return dynamicClient.Delete(ctx, obj.GetName(), metav1.DeleteOptions{}) +} diff --git a/vendor/github.com/cilium/cilium/cilium-cli/k8s/dialer.go b/vendor/github.com/cilium/cilium/cilium-cli/k8s/dialer.go index c1b0325cec..c85f85d4f8 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/k8s/dialer.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/k8s/dialer.go @@ -5,99 +5,21 @@ package k8s import ( "context" - "io" - "net/http" - "strings" - "k8s.io/client-go/tools/portforward" - "k8s.io/client-go/transport/spdy" + "github.com/cilium/cilium/pkg/k8s" ) -// ForwardedPort holds the remote and local mapped port. -type ForwardedPort struct { - Local uint16 - Remote uint16 -} - -// PortForwardParameters are the needed parameters to call PortForward. -// Ports value follow the kubectl syntax: : -// 5000 means 5000:5000 listening on 5000 port locally, forwarding to 5000 in the pod -// 8888:5000 means listening on 8888 port locally, forwarding to 5000 in the pod -// 0:5000 means listening on a random port locally, forwarding to 5000 in the pod -// :5000 means listening on a random port locally, forwarding to 5000 in the pod -type PortForwardParameters struct { - Namespace string - Pod string - Ports []string - Addresses []string - OutWriters OutWriters -} - -// OutWriters holds the two io.Writer needed for the port forward -// one for the output and for the errors. -type OutWriters struct { - Out io.Writer - ErrOut io.Writer -} - -// PortForwardResult are the ports that have been forwarded. -type PortForwardResult struct { - ForwardedPorts []ForwardedPort -} - // PortForward executes in a goroutine a port forward command. // To stop the port-forwarding, use the context by cancelling it -func (c *Client) PortForward(ctx context.Context, p PortForwardParameters) (*PortForwardResult, error) { - req := c.Clientset.CoreV1().RESTClient().Post().Namespace(p.Namespace). - Resource("pods").Name(p.Pod).SubResource(strings.ToLower("PortForward")) - - roundTripper, upgrader, err := spdy.RoundTripperFor(c.Config) - if err != nil { - return nil, err - } - - dialer := spdy.NewDialer(upgrader, &http.Client{Transport: roundTripper}, http.MethodPost, req.URL()) - stopChan, readyChan := make(chan struct{}, 1), make(chan struct{}, 1) - if len(p.Addresses) == 0 { - p.Addresses = []string{"localhost"} - } - - pw, err := portforward.NewOnAddresses(dialer, p.Addresses, p.Ports, stopChan, readyChan, p.OutWriters.Out, p.OutWriters.ErrOut) - if err != nil { - return nil, err - } - - errChan := make(chan error, 1) - go func() { - if err := pw.ForwardPorts(); err != nil { - errChan <- err - } - }() - - go func() { - <-ctx.Done() - close(stopChan) - }() - - select { - case <-pw.Ready: - case <-ctx.Done(): - return nil, ctx.Err() - case err := <-errChan: - return nil, err - } - - ports, err := pw.GetPorts() - if err != nil { - return nil, err - } - - forwardedPorts := make([]ForwardedPort, 0, len(ports)) - for _, port := range ports { - forwardedPorts = append(forwardedPorts, ForwardedPort{port.Local, port.Remote}) - } +func (c *Client) PortForward(ctx context.Context, p k8s.PortForwardParameters) (*k8s.PortForwardResult, error) { + return k8s.NewPortForwarder(c.Clientset, c.Config).PortForward(ctx, p) +} - return &PortForwardResult{ - ForwardedPorts: forwardedPorts, - }, nil +// PortForwardService executes in a goroutine a port forward command towards one of the pod behind a +// service. If `localPort` is 0, a random port is selected. If `svcPort` is 0, uses the first port +// configured on the service. +// +// To stop the port-forwarding, use the context by cancelling it. +func (c *Client) PortForwardService(ctx context.Context, namespace, name string, localPort, svcPort int32) (*k8s.PortForwardServiceResult, error) { + return k8s.NewPortForwarder(c.Clientset, c.Config).PortForwardService(ctx, namespace, name, localPort, svcPort) } diff --git a/vendor/github.com/cilium/cilium/cilium-cli/k8s/helpers.go b/vendor/github.com/cilium/cilium/cilium-cli/k8s/helpers.go index 1c3af9b0d0..c6782cc31c 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/k8s/helpers.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/k8s/helpers.go @@ -4,10 +4,15 @@ package k8s import ( + "fmt" + corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/kubernetes/scheme" ) func NewServiceAccount(name string) *corev1.ServiceAccount { @@ -72,3 +77,27 @@ func NewTLSSecret(name, namespace string, data map[string][]byte) *corev1.Secret Type: corev1.SecretTypeTLS, } } + +// Describe returns the Kubernetes type and resource information for an object +func (c *Client) Describe(obj runtime.Object) (gvk schema.GroupVersionKind, resource schema.GroupVersionResource, err error) { + // first, determine the GroupVersionKind and Resource for the given object + gvks, _, _ := scheme.Scheme.ObjectKinds(obj) + if len(gvks) != 1 { + err = fmt.Errorf("Could not get GroupVersionKind") + return + } + + gvk = gvks[0] + + // Convert the GroupVersionKind in to a Resource + restMapper, err := c.RESTClientGetter.ToRESTMapper() + if err != nil { + return + } + rm, err := restMapper.RESTMapping(gvk.GroupKind(), gvk.Version) + if err != nil { + return + } + resource = rm.Resource + return +} diff --git a/vendor/github.com/cilium/cilium/cilium-cli/multicast/multicast.go b/vendor/github.com/cilium/cilium/cilium-cli/multicast/multicast.go index f5c30dfc12..afc01645a4 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/multicast/multicast.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/multicast/multicast.go @@ -18,6 +18,8 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/cilium/ebpf" + "github.com/cilium/cilium/cilium-cli/defaults" "github.com/cilium/cilium/cilium-cli/k8s" v2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" @@ -29,15 +31,13 @@ import ( // in cilium/cilium repository. const ( - padding = 3 - minWidth = 5 - paddingChar = ' ' - alreadyExistMsg = "already exists" - notExistMsg = "does not exist" + padding = 3 + minWidth = 5 + paddingChar = ' ' ) var ( - errMissingGroup = errors.New(notExistMsg) + errMissingGroup = ebpf.ErrKeyNotExist ) type Multicast struct { @@ -236,7 +236,7 @@ func (m *Multicast) getGroupForSubscriberList(ctx context.Context, pod corev1.Po cmd := []string{"cilium-dbg", "bpf", "multicast", "subscriber", "list", target, "-o", "json"} outputByte, stdErr, err := m.client.ExecInPodWithStderr(ctx, pod.Namespace, pod.Name, defaults.AgentContainerName, cmd) if err != nil { - if strings.Contains(stdErr.String(), notExistMsg) { + if strings.Contains(stdErr.String(), ebpf.ErrKeyNotExist.Error()) { fmt.Fprintf(m.params.Writer, "Multicast group %s does not exist in %s\n", target, pod.Spec.NodeName) return nil, errMissingGroup } @@ -465,7 +465,7 @@ func (m *Multicast) AddAllNodes() error { cmd := []string{"cilium-dbg", "bpf", "multicast", "subscriber", "list", m.params.MulticastGroupIP} _, stdErr, err := m.client.ExecInPodWithStderr(ctx, pod.Namespace, pod.Name, defaults.AgentContainerName, cmd) if err != nil { - if !strings.Contains(stdErr.String(), notExistMsg) { + if !strings.Contains(stdErr.String(), ebpf.ErrKeyNotExist.Error()) { errMsg := fmt.Sprintf("Error: %v, Stderr: %s", err, stdErr.String()) errCh <- errors.New(errMsg) fmt.Fprintf(m.params.Writer, "Fatal error occurred while checking multicast group %s in %s\n", m.params.MulticastGroupIP, pod.Spec.NodeName) @@ -492,7 +492,7 @@ func (m *Multicast) AddAllNodes() error { if err == nil { cnt++ nodeLists = append(nodeLists, ipToNodeMap[ip]) - } else if !strings.Contains(stdErr.String(), alreadyExistMsg) { + } else if !strings.Contains(stdErr.String(), ebpf.ErrKeyExist.Error()) { errMsg := fmt.Sprintf("Error: %v, Stderr: %s", err, stdErr.String()) errCh <- errors.New(errMsg) fmt.Fprintf(m.params.Writer, "Unable to add node %s to multicast group %s in %s by fatal error\n", ip.IP, m.params.MulticastGroupIP, pod.Spec.NodeName) @@ -558,7 +558,7 @@ func (m *Multicast) DelAllNodes() error { cmd := []string{"cilium-dbg", "bpf", "multicast", "group", "delete", m.params.MulticastGroupIP} _, stdErr, err := m.client.ExecInPodWithStderr(ctx, pod.Namespace, pod.Name, defaults.AgentContainerName, cmd) if err != nil { - if !strings.Contains(stdErr.String(), notExistMsg) { + if !strings.Contains(stdErr.String(), ebpf.ErrKeyNotExist.Error()) { errMsg := fmt.Sprintf("Error: %v, Stderr: %s", err, stdErr.String()) errCh <- errors.New(errMsg) fmt.Fprintf(m.params.Writer, "Unable to delete multicast group %s in %s by fatal error\n", m.params.MulticastGroupIP, pod.Spec.NodeName) diff --git a/vendor/github.com/cilium/cilium/cilium-cli/status/k8s.go b/vendor/github.com/cilium/cilium/cilium-cli/status/k8s.go index 31a4fbf300..e1de33cdcf 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/status/k8s.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/status/k8s.go @@ -9,6 +9,7 @@ import ( "fmt" "net/http" "os" + "sort" "strings" "sync" "time" @@ -25,6 +26,7 @@ import ( "github.com/cilium/cilium/api/v1/models" "github.com/cilium/cilium/cilium-cli/defaults" "github.com/cilium/cilium/cilium-cli/k8s" + "github.com/cilium/cilium/pkg/annotation" ciliumv2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" ) @@ -69,11 +71,12 @@ type k8sImplementation interface { CiliumStatus(ctx context.Context, namespace, pod string) (*models.StatusResponse, error) KVStoreMeshStatus(ctx context.Context, namespace, pod string) ([]*models.RemoteCluster, error) CiliumDbgEndpoints(ctx context.Context, namespace, pod string) ([]*models.Endpoint, error) + GetConfigMap(ctx context.Context, namespace, name string, opts metav1.GetOptions) (*corev1.ConfigMap, error) GetDaemonSet(ctx context.Context, namespace, name string, options metav1.GetOptions) (*appsv1.DaemonSet, error) GetDeployment(ctx context.Context, namespace, name string, options metav1.GetOptions) (*appsv1.Deployment, error) ListPods(ctx context.Context, namespace string, options metav1.ListOptions) (*corev1.PodList, error) ListCiliumEndpoints(ctx context.Context, namespace string, options metav1.ListOptions) (*ciliumv2.CiliumEndpointList, error) - CiliumLogs(ctx context.Context, namespace, pod string, since time.Time) (string, error) + CiliumLogs(ctx context.Context, namespace, pod string, since time.Time, previous bool) (string, error) } func NewK8sStatusCollector(client k8sImplementation, params K8sStatusParameters) (*K8sStatusCollector, error) { @@ -308,6 +311,20 @@ func (k *K8sStatusCollector) podStatus(ctx context.Context, status *Status, name return nil } +func (k *K8sStatusCollector) ciliumConfigAnnotations(ctx context.Context, status *Status) error { + cm, err := k.client.GetConfigMap(ctx, k.params.Namespace, defaults.ConfigMapName, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("unable to retrieve ConfigMap %q: %w", defaults.ConfigMapName, err) + } + for k, v := range cm.Annotations { + if strings.HasPrefix(k, annotation.ConfigPrefix) { + status.ConfigErrors = append(status.ConfigErrors, v) + } + } + sort.Strings(status.ConfigErrors) + return nil +} + func (s K8sStatusParameters) waitTimeout() time.Duration { if s.WaitDuration != time.Duration(0) { return s.WaitDuration @@ -350,11 +367,14 @@ func (k *K8sStatusCollector) Status(ctx context.Context) (*Status, error) { for { select { case <-ctx.Done(): + if errors.Is(ctx.Err(), context.Canceled) { + return mostRecentStatus, fmt.Errorf("wait canceled, cilium agent container has crashed or was terminated: %w", ctx.Err()) + } return mostRecentStatus, fmt.Errorf("timeout while waiting for status to become successful: %w", ctx.Err()) default: } - s := k.status(ctx) + s := k.status(ctx, cancel) // We collect the most recent status that even if the last status call // fails, we can still display the most recent status if s != nil { @@ -399,7 +419,7 @@ type statusTask struct { task func(_ context.Context) error } -func (k *K8sStatusCollector) status(ctx context.Context) *Status { +func (k *K8sStatusCollector) status(ctx context.Context, cancel context.CancelFunc) *Status { status := newStatus() tasks := []statusTask{ { @@ -565,6 +585,18 @@ func (k *K8sStatusCollector) status(ctx context.Context) *Status { return nil }, }, + { + name: defaults.ConfigMapName, + task: func(_ context.Context) error { + err := k.ciliumConfigAnnotations(ctx, status) + if err != nil { + status.mutex.Lock() + defer status.mutex.Unlock() + status.CollectionError(err) + } + return nil + }, + }, } tasks = append(tasks, statusTask{ @@ -601,6 +633,7 @@ func (k *K8sStatusCollector) status(ctx context.Context) *Status { var s *models.StatusResponse var eps []*models.Endpoint var err, epserr error + var isTerminated bool if containerStatus != nil && containerStatus.State.Running != nil { // if container is running, execute "cilium status" in the container and parse the result @@ -616,6 +649,7 @@ func (k *K8sStatusCollector) status(ctx context.Context) *Status { if containerStatus != nil { if containerStatus.State.Waiting != nil && containerStatus.State.Waiting.Reason == "CrashLoopBackOff" { desc = "is in CrashLoopBackOff" + isTerminated = true } if containerStatus.LastTerminationState.Terminated != nil { terminated := containerStatus.LastTerminationState.Terminated @@ -625,20 +659,27 @@ func (k *K8sStatusCollector) status(ctx context.Context) *Status { // either from container message or a separate logs request dyingGasp := "" if terminated.Message != "" { - dyingGasp = strings.TrimSpace(terminated.Message) + lastLog = strings.TrimSpace(terminated.Message) } else { agentLogsOnce.Do(func() { // in a sync.Once so we don't waste time retrieving lots of logs - logs, err := k.client.CiliumLogs(ctx, pod.Namespace, pod.Name, terminated.FinishedAt.Time.Add(-2*time.Minute)) + var getPrevious bool + if containerStatus.RestartCount > 0 { + getPrevious = true + } + logs, err := k.client.CiliumLogs(ctx, pod.Namespace, pod.Name, terminated.FinishedAt.Time.Add(-2*time.Minute), getPrevious) if err == nil && logs != "" { dyingGasp = strings.TrimSpace(logs) } }) } - // Only output the last line + // output the last few log lines if available if dyingGasp != "" { lines := strings.Split(dyingGasp, "\n") - lastLog = lines[len(lines)-1] + lastLog = "" + for i := 0; i < min(len(lines), 50); i++ { + lastLog += fmt.Sprintf("\n%s", lines[i]) + } } } } @@ -653,6 +694,11 @@ func (k *K8sStatusCollector) status(ctx context.Context) *Status { status.CiliumStatus[pod.Name] = s status.CiliumEndpoints[pod.Name] = eps + // avoid repeating the status check if the container is in a terminal state + if isTerminated { + cancel() + } + return nil }, }) diff --git a/vendor/github.com/cilium/cilium/cilium-cli/status/status.go b/vendor/github.com/cilium/cilium/cilium-cli/status/status.go index 9ef337bd27..685c3dd5a1 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/status/status.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/status/status.go @@ -140,6 +140,8 @@ type Status struct { // For Helm mode only. HelmChartVersion string `json:"helm_chart_version,omitempty"` + ConfigErrors []string `json:"config_errors,omitempty"` + mutex *lock.Mutex } @@ -433,6 +435,14 @@ func (s *Status) Format() string { } } + header = "Configuration:" + for _, msg := range s.ConfigErrors { + for _, line := range strings.Split(msg, "\n") { + fmt.Fprintf(w, "%s\t \t%s\n", header, line) + header = "" + } + } + w.Flush() return buf.String() diff --git a/vendor/github.com/cilium/cilium/cilium-cli/sysdump/client.go b/vendor/github.com/cilium/cilium/cilium-cli/sysdump/client.go index b2eb592e0e..be7f8459f7 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/sysdump/client.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/sysdump/client.go @@ -12,6 +12,7 @@ import ( appsv1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" + discoveryv1 "k8s.io/api/discovery/v1" networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -71,6 +72,7 @@ type KubernetesClient interface { ListDaemonSet(ctx context.Context, namespace string, o metav1.ListOptions) (*appsv1.DaemonSetList, error) ListEvents(ctx context.Context, o metav1.ListOptions) (*corev1.EventList, error) ListEndpoints(ctx context.Context, o metav1.ListOptions) (*corev1.EndpointsList, error) + ListEndpointSlices(ctx context.Context, o metav1.ListOptions) (*discoveryv1.EndpointSliceList, error) ListIngressClasses(ctx context.Context, o metav1.ListOptions) (*networkingv1.IngressClassList, error) ListIngresses(ctx context.Context, o metav1.ListOptions) (*networkingv1.IngressList, error) ListNamespaces(ctx context.Context, o metav1.ListOptions) (*corev1.NamespaceList, error) diff --git a/vendor/github.com/cilium/cilium/cilium-cli/sysdump/constants.go b/vendor/github.com/cilium/cilium/cilium-cli/sysdump/constants.go index fa537e40d5..5da479d40e 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/sysdump/constants.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/sysdump/constants.go @@ -97,6 +97,7 @@ const ( hubbleGenerateCertsCronJobFileName = "hubble-generate-certs-cronjob-.yaml" hubbleCertificatesFileName = "hubble-certificates-.yaml" kubernetesEndpointsFileName = "k8s-endpoints-.yaml" + kubernetesEndpointSlicesFileName = "k8s-endpointslices-.yaml" kubernetesEventsFileName = "k8s-events-.yaml" kubernetesEventsTableFileName = "k8s-events-.html" kubernetesLeasesFileName = "k8s-leases-.yaml" diff --git a/vendor/github.com/cilium/cilium/cilium-cli/sysdump/sysdump.go b/vendor/github.com/cilium/cilium/cilium-cli/sysdump/sysdump.go index 4e45333a91..408f0a70d2 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/sysdump/sysdump.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/sysdump/sysdump.go @@ -163,6 +163,8 @@ type Collector struct { NodeList []string // CiliumPods is a list of Cilium agent pods running on nodes in NodeList. CiliumPods []*corev1.Pod + // CiliumOperatorPods is the list of Cilium operator pods. + CiliumOperatorPods []*corev1.Pod // CiliumConfigMap is a pointer to cilium-config ConfigMap. CiliumConfigMap *corev1.ConfigMap // additionalTasks keeps track of additional tasks added via AddTasks. @@ -298,6 +300,16 @@ func NewCollector( } } + if c.Options.CiliumOperatorNamespace != "" { + pods, err := c.Client.ListPods(context.Background(), c.Options.CiliumOperatorNamespace, metav1.ListOptions{ + LabelSelector: c.Options.CiliumOperatorLabelSelector, + }) + if err != nil { + return nil, fmt.Errorf("failed to get Cilium operator pods: %w", err) + } + c.CiliumOperatorPods = AllPods(pods) + } + if err := hooks.AddSysdumpTasks(c); err != nil { return nil, fmt.Errorf("failed to add custom sysdump tasks: %w", err) } @@ -519,6 +531,20 @@ func (c *Collector) Run() error { return nil }, }, + { + Description: "Collecting Kubernetes endpointslices", + Quick: true, + Task: func(ctx context.Context) error { + v, err := c.Client.ListEndpointSlices(ctx, metav1.ListOptions{}) + if err != nil { + return fmt.Errorf("failed to collect Kubernetes endpointslices: %w", err) + } + if err := c.WriteYAML(kubernetesEndpointSlicesFileName, v); err != nil { + return fmt.Errorf("failed to collect Kubernetes endpointslices: %w", err) + } + return nil + }, + }, { Description: "Collecting Kubernetes leases", Quick: true, @@ -1002,12 +1028,8 @@ func (c *Collector) Run() error { CreatesSubtasks: true, Description: "Collecting the Cilium operator metrics", Quick: false, - Task: func(ctx context.Context) error { - pods, err := c.Client.ListPods(ctx, c.Options.CiliumOperatorNamespace, metav1.ListOptions{LabelSelector: defaults.OperatorPodSelector}) - if err != nil { - return fmt.Errorf("failed to get the Cilium operator pods: %w", err) - } - err = c.SubmitMetricsSubtask(pods, defaults.OperatorContainerName, defaults.OperatorMetricsPortName) + Task: func(_ context.Context) error { + err := c.SubmitMetricsSubtask(c.CiliumOperatorPods, defaults.OperatorContainerName, defaults.OperatorMetricsPortName) if err != nil { return fmt.Errorf("failed to collect the Cilium operator metrics: %w", err) } @@ -1020,11 +1042,13 @@ func (c *Collector) Run() error { Quick: false, Task: func(ctx context.Context) error { // clustermesh-apiserver runs in the same namespace as operator - pods, err := c.Client.ListPods(ctx, c.Options.CiliumOperatorNamespace, metav1.ListOptions{LabelSelector: defaults.ClusterMeshPodSelector}) + p, err := c.Client.ListPods(ctx, c.Options.CiliumOperatorNamespace, metav1.ListOptions{LabelSelector: defaults.ClusterMeshPodSelector}) if err != nil { return fmt.Errorf("failed to get the Cilium clustermesh pods: %w", err) } + pods := AllPods(p) + err = c.submitClusterMeshAPIServerDbgTasks(pods) if err != nil { return fmt.Errorf("failed to collect the Cilium clustermesh debug information: %w", err) @@ -1047,13 +1071,13 @@ func (c *Collector) Run() error { defaults.ClusterMeshContainerName: ciliumdef.GopsPortApiserver, defaults.ClusterMeshKVStoreMeshContainerName: ciliumdef.GopsPortKVStoreMesh, } { - err = c.SubmitGopsSubtasks(AllPods(pods), container) + err = c.SubmitGopsSubtasks(pods, container) if err != nil { return fmt.Errorf("failed to collect the Cilium clustermesh gops stats: %w", err) } if c.Options.Profiling { - err = c.SubmitStreamProfilingGopsSubtasks(AllPods(pods), container, port) + err = c.SubmitStreamProfilingGopsSubtasks(pods, container, port) if err != nil { return fmt.Errorf("failed to collect the Cilium clustermesh profiles: %w", err) } @@ -1126,14 +1150,8 @@ func (c *Collector) Run() error { Description: "Collecting gops stats from Cilium-operator pods", Quick: true, Task: func(ctx context.Context) error { - p, err := c.Client.ListPods(ctx, c.Options.CiliumNamespace, metav1.ListOptions{ - LabelSelector: c.Options.CiliumOperatorLabelSelector, - }) - if err != nil { - return fmt.Errorf("failed to get cilium-operator pods: %w", err) - } - if err := c.SubmitGopsSubtasks(FilterPods(p, c.NodeList), ciliumOperatorContainerName); err != nil { - return fmt.Errorf("failed to collect Cilium gops: %w", err) + if err := c.SubmitGopsSubtasks(c.CiliumOperatorPods, ciliumOperatorContainerName); err != nil { + return fmt.Errorf("failed to collect cilium-operator gops stats: %w", err) } return nil }, @@ -1185,14 +1203,16 @@ func (c *Collector) Run() error { }, { CreatesSubtasks: true, - Description: "Collecting profiling data from Cilium pods", + Description: "Collecting profiling data from Cilium Operator pods", Quick: false, Task: func(_ context.Context) error { if !c.Options.Profiling { return nil } - if err := c.SubmitProfilingGopsSubtasks(c.CiliumPods, ciliumAgentContainerName); err != nil { - return fmt.Errorf("failed to collect profiling data from Cilium pods: %w", err) + + err := c.SubmitStreamProfilingGopsSubtasks(c.CiliumOperatorPods, ciliumOperatorContainerName, ciliumdef.GopsPortOperator) + if err != nil { + return fmt.Errorf("failed to collect cilium-operator profiles: %w", err) } return nil }, @@ -1247,13 +1267,7 @@ func (c *Collector) Run() error { Description: "Collecting logs from Cilium operator pods", Quick: false, Task: func(ctx context.Context) error { - p, err := c.Client.ListPods(ctx, c.Options.CiliumNamespace, metav1.ListOptions{ - LabelSelector: c.Options.CiliumOperatorLabelSelector, - }) - if err != nil { - return fmt.Errorf("failed to get logs from Cilium operator pods") - } - if err := c.SubmitLogsTasks(AllPods(p), c.Options.LogsSinceTime, c.Options.LogsLimitBytes); err != nil { + if err := c.SubmitLogsTasks(c.CiliumOperatorPods, c.Options.LogsSinceTime, c.Options.LogsLimitBytes); err != nil { return fmt.Errorf("failed to collect logs from Cilium operator pods") } return nil @@ -1404,6 +1418,19 @@ func (c *Collector) Run() error { tasks = append(tasks, ciliumTasks...) serialTasks = append(serialTasks, Task{ + CreatesSubtasks: true, + Description: "Collecting profiling data from Cilium pods", + Quick: false, + Task: func(_ context.Context) error { + if !c.Options.Profiling { + return nil + } + if err := c.SubmitProfilingGopsSubtasks(c.CiliumPods, ciliumAgentContainerName); err != nil { + return fmt.Errorf("failed to collect profiling data from Cilium pods: %w", err) + } + return nil + }, + }, Task{ CreatesSubtasks: true, Description: "Collecting tracing data from Cilium pods", Quick: false, @@ -2828,10 +2855,10 @@ func (c *Collector) submitKVStoreTasks(ctx context.Context, pod *corev1.Pod) err } // SubmitMetricsSubtask submits tasks to collect metrics from pods. -func (c *Collector) SubmitMetricsSubtask(pods *corev1.PodList, containerName, portName string) error { - for _, p := range pods.Items { +func (c *Collector) SubmitMetricsSubtask(pods []*corev1.Pod, containerName, portName string) error { + for _, p := range pods { p := p - if !podIsRunningAndHasContainer(&p, containerName) { + if !podIsRunningAndHasContainer(p, containerName) { continue } err := c.Pool.Submit(fmt.Sprintf("metrics-%s-%s-%s", p.Name, containerName, portName), func(ctx context.Context) error { @@ -2855,7 +2882,7 @@ func (c *Collector) SubmitMetricsSubtask(pods *corev1.PodList, containerName, po return nil } -func (c *Collector) submitClusterMeshAPIServerDbgTasks(pods *corev1.PodList) error { +func (c *Collector) submitClusterMeshAPIServerDbgTasks(pods []*corev1.Pod) error { tasks := []struct { name string ext string @@ -2899,9 +2926,9 @@ func (c *Collector) submitClusterMeshAPIServerDbgTasks(pods *corev1.PodList) err }, } - for _, pod := range pods.Items { + for _, pod := range pods { for _, task := range tasks { - if !podIsRunningAndHasContainer(&pod, task.container) { + if !podIsRunningAndHasContainer(pod, task.container) { continue } @@ -2936,7 +2963,7 @@ func (c *Collector) submitClusterMeshAPIServerDbgTasks(pods *corev1.PodList) err return nil } -func getPodMetricsPort(pod corev1.Pod, containerName, portName string) (int32, error) { +func getPodMetricsPort(pod *corev1.Pod, containerName, portName string) (int32, error) { for _, container := range pod.Spec.Containers { if container.Name != containerName { continue diff --git a/vendor/github.com/cilium/cilium/cilium-cli/utils/features/features.go b/vendor/github.com/cilium/cilium/cilium-cli/utils/features/features.go index 20c72569a6..cfc342fa7f 100644 --- a/vendor/github.com/cilium/cilium/cilium-cli/utils/features/features.go +++ b/vendor/github.com/cilium/cilium/cilium-cli/utils/features/features.go @@ -42,8 +42,9 @@ const ( HealthChecking Feature = "health-checking" - EncryptionPod Feature = "encryption-pod" - EncryptionNode Feature = "encryption-node" + EncryptionPod Feature = "encryption-pod" + EncryptionNode Feature = "encryption-node" + EncryptionStrictMode Feature = "enable-encryption-strict-mode" IPv4 Feature = "ipv4" IPv6 Feature = "ipv6" @@ -80,6 +81,8 @@ const ( BGPControlPlane Feature = "enable-bgp-control-plane" NodeLocalDNS Feature = "node-local-dns" + + Multicast Feature = "multicast-enabled" ) // Feature is the name of a Cilium Feature (e.g. l7-proxy, cni chaining mode etc) @@ -320,6 +323,14 @@ func (fs Set) ExtractFromConfigMap(cm *v1.ConfigMap) { fs[BGPControlPlane] = Status{ Enabled: cm.Data[string(BGPControlPlane)] == "true", } + + fs[Multicast] = Status{ + Enabled: cm.Data[string(Multicast)] == "true", + } + + fs[EncryptionStrictMode] = Status{ + Enabled: cm.Data[string(EncryptionStrictMode)] == "true", + } } func (fs Set) ExtractFromNodes(nodesWithoutCilium map[string]struct{}) { diff --git a/vendor/github.com/cilium/cilium/hubble/pkg/printer/printer.go b/vendor/github.com/cilium/cilium/hubble/pkg/printer/printer.go index d1aab44024..d08940b9e9 100644 --- a/vendor/github.com/cilium/cilium/hubble/pkg/printer/printer.go +++ b/vendor/github.com/cilium/cilium/hubble/pkg/printer/printer.go @@ -800,6 +800,8 @@ func (p *Printer) WriteGetFlowsResponse(res *observerpb.GetFlowsResponse) error return p.WriteProtoFlow(res) case *observerpb.GetFlowsResponse_NodeStatus: return p.WriteProtoNodeStatusEvent(res) + case *observerpb.GetFlowsResponse_LostEvents: + return p.WriteLostEvent(res) case nil: return nil default: @@ -914,3 +916,89 @@ func (p *Printer) WriteServerStatusResponse(res *observerpb.ServerStatusResponse } return nil } + +// WriteLostEvent writes v1.Flow into the output writer. +func (p *Printer) WriteLostEvent(res *observerpb.GetFlowsResponse) error { + f := res.GetLostEvents() + + switch p.opts.output { + case TabOutput: + ew := &errWriter{w: p.tw} + src := f.GetSource() + numEventsLost := f.GetNumEventsLost() + cpu := f.GetCpu() + + if p.line == 0 { + ew.write("TIMESTAMP", tab) + if p.opts.nodeName { + ew.write("NODE", tab) + } + ew.write( + "SOURCE", tab, + "DESTINATION", tab, + "TYPE", tab, + "VERDICT", tab, + "SUMMARY", newline, + ) + } + ew.write("", tab) + if p.opts.nodeName { + ew.write("", tab) + } + ew.write( + src, tab, + "", tab, + "EVENTS LOST", tab, + "", tab, + fmt.Sprintf("CPU(%d) - %d", cpu.GetValue(), numEventsLost), newline, + ) + if ew.err != nil { + return fmt.Errorf("failed to write out packet: %w", ew.err) + } + case DictOutput: + ew := &errWriter{w: p.opts.w} + src := f.GetSource() + numEventsLost := f.GetNumEventsLost() + cpu := f.GetCpu() + if p.line != 0 { + // TODO: line length? + ew.write(dictSeparator, newline) + } + + // this is a little crude, but will do for now. should probably find the + // longest header and auto-format the keys + ew.write(" TIMESTAMP: ", "", newline) + if p.opts.nodeName { + ew.write(" NODE: ", "", newline) + } + ew.write( + " SOURCE: ", src, newline, + " TYPE: ", "EVENTS LOST", newline, + " VERDICT: ", "", newline, + " SUMMARY: ", fmt.Sprintf("CPU(%d) - %d", cpu.GetValue(), numEventsLost), newline, + ) + if ew.err != nil { + return fmt.Errorf("failed to write out packet: %w", ew.err) + } + case CompactOutput: + src := f.GetSource() + numEventsLost := f.GetNumEventsLost() + cpu := f.GetCpu() + + _, err := fmt.Fprintf(p.opts.w, + "EVENTS LOST: %s CPU(%d) %d\n", + src, + cpu.GetValue(), + numEventsLost, + ) + if err != nil { + return fmt.Errorf("failed to write out packet: %w", err) + } + case JSONLegacyOutput: + return p.jsonEncoder.Encode(f) + case JSONPBOutput: + return p.jsonEncoder.Encode(res) + } + p.line++ + return nil +} diff --git a/vendor/github.com/cilium/cilium/pkg/allocator/allocator.go b/vendor/github.com/cilium/cilium/pkg/allocator/allocator.go index 5e015b83a2..13015dfbfb 100644 --- a/vendor/github.com/cilium/cilium/pkg/allocator/allocator.go +++ b/vendor/github.com/cilium/cilium/pkg/allocator/allocator.go @@ -13,7 +13,6 @@ import ( "github.com/cilium/cilium/pkg/backoff" "github.com/cilium/cilium/pkg/idpool" - "github.com/cilium/cilium/pkg/inctimer" "github.com/cilium/cilium/pkg/kvstore" "github.com/cilium/cilium/pkg/lock" "github.com/cilium/cilium/pkg/logging" @@ -28,9 +27,9 @@ var ( ) const ( - // maxAllocAttempts is the number of attempted allocation requests - // performed before failing. - maxAllocAttempts = 16 + // defaultMaxAllocAttempts is the default number of attempted allocation + // requests performed before failing. + defaultMaxAllocAttempts = 16 ) // Allocator is a distributed ID allocator backed by a KVstore. It maps @@ -151,6 +150,13 @@ type Allocator struct { // disableAutostart prevents starting the allocator when it is initialized disableAutostart bool + // operatorIDManagement indicates if cilium-operator is managing Cilium Identities. + operatorIDManagement bool + + // maxAllocAttempts is the number of attempted allocation requests + // performed before failing. + maxAllocAttempts int + // cacheValidators implement extra validations of retrieved identities, e.g., // to ensure that they belong to the expected range. cacheValidators []CacheValidator @@ -315,6 +321,7 @@ func NewAllocator(typ AllocatorKey, backend Backend, opts ...AllocatorOption) (* Min: time.Duration(20) * time.Millisecond, Factor: 2.0, }, + maxAllocAttempts: defaultMaxAllocAttempts, } for _, fn := range opts { @@ -400,6 +407,18 @@ func WithMasterKeyProtection() AllocatorOption { return func(a *Allocator) { a.enableMasterKeyProtection = true } } +// WithOperatorIDManagement enables the mode with cilium-operator managing +// Cilium Identities. +func WithOperatorIDManagement() AllocatorOption { + return func(a *Allocator) { a.operatorIDManagement = true } +} + +// WithMaxAllocAttempts sets the maxAllocAttempts. If not set, new Allocator +// will use defaultMaxAllocAttempts. +func WithMaxAllocAttempts(maxAttempts int) AllocatorOption { + return func(a *Allocator) { a.maxAllocAttempts = maxAttempts } +} + // WithoutGC disables the use of the garbage collector func WithoutGC() AllocatorOption { return func(a *Allocator) { a.disableGC = true } @@ -665,13 +684,22 @@ func (a *Allocator) Allocate(ctx context.Context, key AllocatorKey) (idpool.ID, return 0, false, false, fmt.Errorf("allocation was cancelled while waiting for initial key list to be received: %w", ctx.Err()) } + if a.operatorIDManagement { + id, err := a.GetWithRetry(ctx, key) + // The second and third return values are always false when + // operatorIDManagement is enabled because cilium-operator manages security + // IDs, and they are never newly allocated or require holding a reference to + // a key. + return id, false, false, err + } + kvstore.Trace("Allocating from kvstore", nil, logrus.Fields{fieldKey: key}) // make a copy of the template and customize it boff := a.backoffTemplate boff.Name = key.String() - for attempt := 0; attempt < maxAllocAttempts; attempt++ { + for attempt := 0; attempt < a.maxAllocAttempts; attempt++ { // Check our list of local keys already in use and increment the // refcnt. The returned key must be released afterwards. No kvstore // operation was performed for this allocation. @@ -716,6 +744,55 @@ func (a *Allocator) Allocate(ctx context.Context, key AllocatorKey) (idpool.ID, return 0, false, false, err } +func (a *Allocator) GetWithRetry(ctx context.Context, key AllocatorKey) (idpool.ID, error) { + getID := func() (idpool.ID, error) { + id, err := a.Get(ctx, key) + if err != nil { + return idpool.NoID, err + } + + if id == idpool.NoID { + return idpool.NoID, fmt.Errorf("security identity not found for key %s", key.String()) + } + + return id, nil + } + + // Make a copy of the template and customize it. + boff := a.backoffTemplate + boff.Name = key.String() + + var id idpool.ID + var err error + + for attempt := 0; attempt < a.maxAllocAttempts; attempt++ { + id, err = getID() + if err == nil { + return id, nil + } + + scopedLog := log.WithFields(logrus.Fields{ + fieldKey: key, + logfields.Attempt: attempt, + }) + + select { + case <-ctx.Done(): + scopedLog.WithError(ctx.Err()).Warning("Ongoing key allocation has been cancelled") + return idpool.NoID, fmt.Errorf("key allocation cancelled: %w", ctx.Err()) + default: + scopedLog.WithError(err).Debug("CiliumIdentity not yet created by cilium-operator, retrying...") + } + + if waitErr := boff.Wait(ctx); waitErr != nil { + scopedLog.Warning("timed out waiting for cilium-operator to allocate CiliumIdentity") + return idpool.NoID, fmt.Errorf("timed out waiting for cilium-operator to allocate CiliumIdentity for key %v, error: %w", key.GetKey(), waitErr) + } + } + + return idpool.NoID, err +} + // GetIfLocked returns the ID which is allocated to a key. Returns an ID of NoID if no ID // has been allocated to this key yet if the client is still holding the given // lock. @@ -817,6 +894,11 @@ func (a *Allocator) GetByIDIncludeRemoteCaches(ctx context.Context, id idpool.ID // the last user has released the ID, the key is removed in the KVstore and // the returned lastUse value is true. func (a *Allocator) Release(ctx context.Context, key AllocatorKey) (lastUse bool, err error) { + if a.operatorIDManagement { + log.WithField(fieldKey, key).Debug("Skipping key release when cilium-operator ID management is enabled") + return false, nil + } + log.WithField(fieldKey, key).Info("Releasing key") select { @@ -888,8 +970,6 @@ func (a *Allocator) syncLocalKeys() error { func (a *Allocator) startLocalKeySync() { go func(a *Allocator) { - kvTimer, kvTimerDone := inctimer.New() - defer kvTimerDone() for { if err := a.syncLocalKeys(); err != nil { log.WithError(err).Warning("Unable to run local key sync routine") @@ -899,7 +979,7 @@ func (a *Allocator) startLocalKeySync() { case <-a.stopGC: log.Debug("Stopped master key sync routine") return - case <-kvTimer.After(option.Config.KVstorePeriodicSync): + case <-time.After(option.Config.KVstorePeriodicSync): } } }(a) diff --git a/vendor/github.com/cilium/cilium/pkg/annotation/k8s.go b/vendor/github.com/cilium/cilium/pkg/annotation/k8s.go index 6894f3c6cf..59c3ad238b 100644 --- a/vendor/github.com/cilium/cilium/pkg/annotation/k8s.go +++ b/vendor/github.com/cilium/cilium/pkg/annotation/k8s.go @@ -140,13 +140,6 @@ const ( // use SNAT so that reply traffic comes back ServiceForwardingMode = ServicePrefix + "/forwarding-mode" - // ProxyVisibility / ProxyVisibilityAlias is the annotation name used to - // indicate whether proxy visibility should be enabled for a given pod (i.e., - // all traffic for the pod is redirected to the proxy for the given port / - // protocol in the annotation - ProxyVisibility = PolicyPrefix + "/proxy-visibility" - ProxyVisibilityAlias = Prefix + ".proxy-visibility" - // NoTrack / NoTrackAlias is the annotation name used to store the port and // protocol that we should bypass kernel conntrack for a given pod. This // applies for both TCP and UDP connection. Current use case is NodeLocalDNS. diff --git a/vendor/github.com/cilium/cilium/pkg/aws/eni/types/types.go b/vendor/github.com/cilium/cilium/pkg/aws/eni/types/types.go index c2cba6c384..5fecd2467a 100644 --- a/vendor/github.com/cilium/cilium/pkg/aws/eni/types/types.go +++ b/vendor/github.com/cilium/cilium/pkg/aws/eni/types/types.go @@ -206,6 +206,11 @@ type ENI struct { // // +optional Tags map[string]string `json:"tags,omitempty"` + + // PublicIP is the public IP associated with the ENI + // + // +optional + PublicIP string `json:"public-ip,omitempty"` } func (e *ENI) DeepCopyInterface() types.Interface { diff --git a/vendor/github.com/cilium/cilium/pkg/aws/eni/types/zz_generated.deepequal.go b/vendor/github.com/cilium/cilium/pkg/aws/eni/types/zz_generated.deepequal.go index 4c1c93fc0e..00b50c30d5 100644 --- a/vendor/github.com/cilium/cilium/pkg/aws/eni/types/zz_generated.deepequal.go +++ b/vendor/github.com/cilium/cilium/pkg/aws/eni/types/zz_generated.deepequal.go @@ -163,6 +163,10 @@ func (in *ENI) DeepEqual(other *ENI) bool { } } + if in.PublicIP != other.PublicIP { + return false + } + return true } diff --git a/vendor/github.com/cilium/cilium/pkg/bgpv1/types/fake_router.go b/vendor/github.com/cilium/cilium/pkg/bgpv1/types/fake_router.go index 97a86388c5..f1bf0dc4ef 100644 --- a/vendor/github.com/cilium/cilium/pkg/bgpv1/types/fake_router.go +++ b/vendor/github.com/cilium/cilium/pkg/bgpv1/types/fake_router.go @@ -5,10 +5,14 @@ package types import "context" -type FakeRouter struct{} +type FakeRouter struct { + paths map[string]*Path +} func NewFakeRouter() Router { - return &FakeRouter{} + return &FakeRouter{ + paths: make(map[string]*Path), + } } func (f *FakeRouter) Stop() {} @@ -31,10 +35,13 @@ func (f *FakeRouter) ResetNeighbor(ctx context.Context, r ResetNeighborRequest) func (f *FakeRouter) AdvertisePath(ctx context.Context, p PathRequest) (PathResponse, error) { path := p.Path + f.paths[path.NLRI.String()] = path return PathResponse{path}, nil } func (f *FakeRouter) WithdrawPath(ctx context.Context, p PathRequest) error { + path := p.Path + delete(f.paths, path.NLRI.String()) return nil } @@ -51,7 +58,14 @@ func (f *FakeRouter) GetPeerState(ctx context.Context) (GetPeerStateResponse, er } func (f *FakeRouter) GetRoutes(ctx context.Context, r *GetRoutesRequest) (*GetRoutesResponse, error) { - return nil, nil + var routes []*Route + for _, path := range f.paths { + routes = append(routes, &Route{ + Prefix: path.NLRI.String(), + Paths: []*Path{path}, + }) + } + return &GetRoutesResponse{Routes: routes}, nil } func (f *FakeRouter) GetRoutePolicies(ctx context.Context) (*GetRoutePoliciesResponse, error) { diff --git a/vendor/github.com/cilium/cilium/pkg/bgpv1/types/log.go b/vendor/github.com/cilium/cilium/pkg/bgpv1/types/log.go index cc5cf76dd0..9f7a9384e4 100644 --- a/vendor/github.com/cilium/cilium/pkg/bgpv1/types/log.go +++ b/vendor/github.com/cilium/cilium/pkg/bgpv1/types/log.go @@ -42,4 +42,7 @@ const ( // PolicyLogField is used as key for BGP policy in the log field. PolicyLogField = "policy" + + // ResourceLogField is used as key for k8s resource in the log field. + ResourceLogField = "resource" ) diff --git a/vendor/github.com/cilium/cilium/pkg/clustermesh/types/option.go b/vendor/github.com/cilium/cilium/pkg/clustermesh/types/option.go index 00b5673f6a..b00ee5c92e 100644 --- a/vendor/github.com/cilium/cilium/pkg/clustermesh/types/option.go +++ b/vendor/github.com/cilium/cilium/pkg/clustermesh/types/option.go @@ -4,13 +4,13 @@ package types import ( + "errors" "fmt" - "github.com/sirupsen/logrus" "github.com/spf13/pflag" "github.com/cilium/cilium/pkg/defaults" - "github.com/cilium/cilium/pkg/logging/logfields" + ipamOption "github.com/cilium/cilium/pkg/ipam/option" ) const ( @@ -47,29 +47,42 @@ func (def ClusterInfo) Flags(flags *pflag.FlagSet) { // Validate validates that the ClusterID is in the valid range (including ClusterID == 0), // and that the ClusterName is different from the default value if the ClusterID != 0. -func (c ClusterInfo) Validate(log logrus.FieldLogger) error { +func (c ClusterInfo) Validate() error { if c.ID < ClusterIDMin || c.ID > ClusterIDMax { return fmt.Errorf("invalid cluster id %d: must be in range %d..%d", c.ID, ClusterIDMin, ClusterIDMax) } - return c.validateName(log) + return c.validateName() } // ValidateStrict validates that the ClusterID is in the valid range, but not 0, // and that the ClusterName is different from the default value. -func (c ClusterInfo) ValidateStrict(log logrus.FieldLogger) error { +func (c ClusterInfo) ValidateStrict() error { if err := ValidateClusterID(c.ID); err != nil { return err } - return c.validateName(log) + return c.validateName() } -func (c ClusterInfo) validateName(log logrus.FieldLogger) error { +// ValidateBuggyClusterID returns an error if a buggy cluster ID (i.e., with the +// 7th bit set) is used in combination with ENI IPAM mode or AWS CNI chaining. +func (c ClusterInfo) ValidateBuggyClusterID(ipamMode, chainingMode string) error { + if (c.ID&0x80) != 0 && (ipamMode == ipamOption.IPAMENI || ipamMode == ipamOption.IPAMAlibabaCloud || chainingMode == "aws-cni") { + return errors.New("Cilium is currently affected by a bug that causes traffic matched " + + "by network policies to be incorrectly dropped when running in either ENI mode (both " + + "AWS and AlibabaCloud) or AWS VPC CNI chaining mode, if the cluster ID is 128-255 (and " + + "384-511 when max-connected-clusters=511). " + + "Please refer to https://github.com/cilium/cilium/issues/21330 for additional details.") + } + + return nil +} + +func (c ClusterInfo) validateName() error { if err := ValidateClusterName(c.Name); err != nil { - log.WithField(logfields.ClusterName, c.Name).WithError(err). - Error("Invalid cluster name. This may cause degraded functionality, and will be strictly forbidden starting from Cilium v1.17") + return fmt.Errorf("invalid cluster name: %w", err) } if c.ID != 0 && c.Name == defaults.ClusterName { diff --git a/vendor/github.com/cilium/cilium/pkg/container/bitlpm/cidr.go b/vendor/github.com/cilium/cilium/pkg/container/bitlpm/cidr.go index c80b199ac3..ee14422af0 100644 --- a/vendor/github.com/cilium/cilium/pkg/container/bitlpm/cidr.go +++ b/vendor/github.com/cilium/cilium/pkg/container/bitlpm/cidr.go @@ -54,6 +54,10 @@ func (c *CIDRTrie[T]) Ancestors(cidr netip.Prefix, fn func(k netip.Prefix, v T) }) } +func (c *CIDRTrie[T]) AncestorIterator(cidr netip.Prefix) Iterator[Key[netip.Prefix], T] { + return c.treeForFamily(cidr).AncestorIterator(uint(cidr.Bits()), cidrKey(cidr)) +} + // AncestorsLongestPrefixFirst iterates over every CIDR pair that contains the CIDR argument, // longest matching prefix first, then iterating towards the root of the trie. func (c *CIDRTrie[T]) AncestorsLongestPrefixFirst(cidr netip.Prefix, fn func(k netip.Prefix, v T) bool) { @@ -62,6 +66,10 @@ func (c *CIDRTrie[T]) AncestorsLongestPrefixFirst(cidr netip.Prefix, fn func(k n }) } +func (c *CIDRTrie[T]) AncestorLongestPrefixFirstIterator(cidr netip.Prefix) Iterator[Key[netip.Prefix], T] { + return c.treeForFamily(cidr).AncestorLongestPrefixFirstIterator(uint(cidr.Bits()), cidrKey(cidr)) +} + // Descendants iterates over every CIDR that is contained by the CIDR argument. func (c *CIDRTrie[T]) Descendants(cidr netip.Prefix, fn func(k netip.Prefix, v T) bool) { c.treeForFamily(cidr).Descendants(uint(cidr.Bits()), cidrKey(cidr), func(prefix uint, k Key[netip.Prefix], v T) bool { @@ -69,6 +77,10 @@ func (c *CIDRTrie[T]) Descendants(cidr netip.Prefix, fn func(k netip.Prefix, v T }) } +func (c *CIDRTrie[T]) DescendantIterator(cidr netip.Prefix) Iterator[Key[netip.Prefix], T] { + return c.treeForFamily(cidr).DescendantIterator(uint(cidr.Bits()), cidrKey(cidr)) +} + // DescendantsShortestPrefixFirst iterates over every CIDR that is contained by the CIDR argument. func (c *CIDRTrie[T]) DescendantsShortestPrefixFirst(cidr netip.Prefix, fn func(k netip.Prefix, v T) bool) { c.treeForFamily(cidr).DescendantsShortestPrefixFirst(uint(cidr.Bits()), cidrKey(cidr), func(prefix uint, k Key[netip.Prefix], v T) bool { @@ -76,6 +88,10 @@ func (c *CIDRTrie[T]) DescendantsShortestPrefixFirst(cidr netip.Prefix, fn func( }) } +func (c *CIDRTrie[T]) DescendantShortestPrefixFirstIterator(cidr netip.Prefix) Iterator[Key[netip.Prefix], T] { + return c.treeForFamily(cidr).DescendantShortestPrefixFirstIterator(uint(cidr.Bits()), cidrKey(cidr)) +} + // Upsert adds or updates the value for a given prefix. func (c *CIDRTrie[T]) Upsert(cidr netip.Prefix, v T) bool { return c.treeForFamily(cidr).Upsert(uint(cidr.Bits()), cidrKey(cidr), v) diff --git a/vendor/github.com/cilium/cilium/pkg/container/bitlpm/trie.go b/vendor/github.com/cilium/cilium/pkg/container/bitlpm/trie.go index 3becf75182..d64c7f0e3b 100644 --- a/vendor/github.com/cilium/cilium/pkg/container/bitlpm/trie.go +++ b/vendor/github.com/cilium/cilium/pkg/container/bitlpm/trie.go @@ -44,12 +44,19 @@ type Trie[K, T any] interface { // Note: If the prefix argument exceeds the Trie's maximum // prefix, it will be set to the Trie's maximum prefix. Ancestors(prefix uint, key K, fn func(uint, K, T) bool) + // AncestorIterator returns an iterator for ancestors that + // can be used to produce the 'Next' key/value pair in sequence. + AncestorIterator(prefix uint, key K) Iterator[K, T] // AncestorsLongestPrefixFirst iterates over every prefix-key pair that // contains the prefix-key argument pair. If the function argument // returns false the iteration will stop. AncestorsLongestPrefixFirst // iterates keys from longest to shortest prefix match (that is, the // longest matching prefix will be returned first). AncestorsLongestPrefixFirst(prefix uint, key K, fn func(uint, K, T) bool) + // AncestorLongestPrefixFirstIterator returns an iterator for ancestors + // that can be used to produce the 'Next' key/value pair in sequence, + // starting from the key with the longest common prefix with 'key'. + AncestorLongestPrefixFirstIterator(prefix uint, key K) Iterator[K, T] // Descendants iterates over every prefix-key pair that is contained // by the prefix-key argument pair. If the function argument // returns false the iteration will stop. Descendants does **not** iterate @@ -58,12 +65,19 @@ type Trie[K, T any] interface { // Note: If the prefix argument exceeds the Trie's maximum // prefix, it will be set to the Trie's maximum prefix. Descendants(prefix uint, key K, fn func(uint, K, T) bool) + // DescendantIterator returns an iterator for descendants + // that can be used to produce the 'Next' key/value pair in sequence. + DescendantIterator(prefix uint, key K) Iterator[K, T] // DescendantsShortestPrefixFirst iterates over every prefix-key pair that is contained by // the prefix-key argument pair. If the function argument returns false the iteration will // stop. DescendantsShortestPrefixFirst iterates keys starting from shortest prefix, and // progressing towards keys with longer prefixes. Keys with equal prefix lengths are not // iterated in any particular order. DescendantsShortestPrefixFirst(prefix uint, key K, fn func(uint, K, T) bool) + // DescendantShortestPrefixFirstIterator returns an iterator for descendants + // that can be used to produce the 'Next' key/value pair in sequence, + // starting from the key with the shortest common prefix with 'key'. + DescendantShortestPrefixFirstIterator(prefix uint, key K) Iterator[K, T] // Upsert updates or inserts the trie with a a prefix, key, // and value. The method returns true if the key is new, and // false if the key already existed. @@ -84,6 +98,16 @@ type Trie[K, T any] interface { ForEach(fn func(uint, K, T) bool) } +// Iterator is an interface that can be used to produce the next key/value pair in iteration +// sequence. 'ok' is 'false' when the sequence ends; 'key' and 'value' are returned with empty +// values in that case. +// Iteration state is held in the implementation explicitly, rather than in Go stack/closures. +// Policy mapstate generation benchmark BenchmarkRegenerateCIDRDenyPolicyRules reports 25% less allocations +// with Iterator, even in combination with Go 1.23 Iterators on the caller side. +type Iterator[K, T any] interface { + Next() (ok bool, key K, value T) +} + // Key is an interface that implements all the necessary // methods to index and retrieve keys. type Key[K any] interface { @@ -171,6 +195,204 @@ func (t *trie[K, T]) Ancestors(prefixLen uint, k Key[K], fn func(prefix uint, ke }) } +// ancestorIterator implements Iteraror for ancestor iteration +type ancestorIterator[K, T any] struct { + key Key[K] + prefixLen uint + maxPrefix uint + currentNode *node[K, T] +} + +// AncestorIterator returns an iterator for ancestors. +func (t *trie[K, T]) AncestorIterator(prefixLen uint, k Key[K]) Iterator[Key[K], T] { + if k != nil { + return &ancestorIterator[K, T]{ + prefixLen: min(prefixLen, t.maxPrefix), + key: k, + maxPrefix: t.maxPrefix, + currentNode: t.root, + } + } + return &ancestorIterator[K, T]{} +} + +// Next produces the 'Next' key/value pair in the iteration sequence maintained by 'iter'. 'ok' is +// 'false' when the sequence ends; 'key' and 'value' are returned with empty values in that case. +func (i *ancestorIterator[K, T]) Next() (ok bool, key Key[K], value T) { + for i.currentNode != nil { + k := i.key + prefixLen := i.prefixLen + currentNode := i.currentNode + + matchLen := currentNode.prefixMatch(prefixLen, k) + // The current-node does not match. + if matchLen < currentNode.prefixLen { + break + } + // Skip over intermediate nodes + if currentNode.intermediate { + i.currentNode = currentNode.children[k.BitValueAt(currentNode.prefixLen)] + continue + } + if matchLen == i.maxPrefix { + i.currentNode = nil + } else { + i.currentNode = currentNode.children[k.BitValueAt(currentNode.prefixLen)] + } + return true, currentNode.key, currentNode.value + } + return false, key, value +} + +// ancestorLPFIterator implements Iteraror for ancestor iteration for longest-prefix-first iteration +// order. +type ancestorLPFIterator[K, T any] struct { + stack nodes[K, T] +} + +// AncestorLongestPrefixFirstIterator returns an iterator for ancestors +// that can be used to produce the 'Next' key/value pair in sequence, +// starting from the key with the longest common prefix with 'key'. +func (t *trie[K, T]) AncestorLongestPrefixFirstIterator(prefixLen uint, k Key[K]) Iterator[Key[K], T] { + iter := &ancestorLPFIterator[K, T]{} + if k != nil { + for currentNode := t.root; currentNode != nil; currentNode = currentNode.children[k.BitValueAt(currentNode.prefixLen)] { + matchLen := currentNode.prefixMatch(prefixLen, k) + // The current-node does not match. + if matchLen < currentNode.prefixLen { + break + } + // Skip over intermediate nodes + if currentNode.intermediate { + continue + } + iter.stack.push(currentNode) + if matchLen == t.maxPrefix { + break + } + } + } + return iter +} + +// Next produces the 'Next' key/value pair in the iteration sequence maintained by 'iter'. 'ok' is +// 'false' when the sequence ends; 'key' and 'value' are returned with empty values in that case. +func (i *ancestorLPFIterator[K, T]) Next() (ok bool, key Key[K], value T) { + if len(i.stack) > 0 { + n := i.stack.pop() + return true, n.key, n.value + } + return false, key, value +} + +// descendantIterator implements Iteraror for descendants iteration +type descendantIterator[K, T any] struct { + nodes nodes[K, T] +} + +// DescendantIterator returns an iterator for descendants +// that can be used to produce the 'Next' key/value pair in sequence. +func (t *trie[K, T]) DescendantIterator(prefixLen uint, k Key[K]) Iterator[Key[K], T] { + iter := &descendantIterator[K, T]{} + if k != nil { + prefixLen = min(prefixLen, t.maxPrefix) + currentNode := t.root + for currentNode != nil { + matchLen := currentNode.prefixMatch(prefixLen, k) + // CurrentNode matches the prefix-key argument + if matchLen >= prefixLen { + iter.nodes.push(currentNode) + break + } + // currentNode is a leaf and has no children. Calling k.BitValueAt may + // overrun the key storage. + if currentNode.prefixLen >= t.maxPrefix { + break + } + currentNode = currentNode.children[k.BitValueAt(currentNode.prefixLen)] + } + } + return iter +} + +// Next produces the 'Next' key/value pair in the iteration sequence maintained by 'iter'. 'ok' is +// 'false' when the sequence ends; 'key' and 'value' are returned with empty values in that case. +func (i *descendantIterator[K, T]) Next() (ok bool, key Key[K], value T) { + for len(i.nodes) > 0 { + // pop the latest node + n := i.nodes.pop() + // push the children, if any + if n.children[0] != nil { + i.nodes.push(n.children[0]) + } + if n.children[1] != nil { + i.nodes.push(n.children[1]) + } + // Skip over intermediate nodes + if n.intermediate { + continue + } + return true, n.key, n.value + } + return false, key, value +} + +// descendantSPFIterator implements Iteraror for descendants iteration for shortest-prefix-first +// iteration order. +type descendantSPFIterator[K, T any] struct { + heap nodes[K, T] +} + +// DescendantsShortestPrefixFirst iterates over every prefix-key pair that is contained by +// the prefix-key argument pair. If the function argument returns false the iteration will +// stop. DescendantsShortestPrefixFirst iterates keys starting from shortest prefix, and +// progressing towards keys with longer prefixes. Keys with equal prefix lengths are not +// iterated in any particular order. +func (t *trie[K, T]) DescendantShortestPrefixFirstIterator(prefixLen uint, k Key[K]) Iterator[Key[K], T] { + iter := &descendantSPFIterator[K, T]{} + if k != nil { + prefixLen = min(prefixLen, t.maxPrefix) + currentNode := t.root + for currentNode != nil { + matchLen := currentNode.prefixMatch(prefixLen, k) + // CurrentNode matches the prefix-key argument + if matchLen >= prefixLen { + iter.heap.push(currentNode) + break + } + // currentNode is a leaf and has no children. Calling k.BitValueAt may + // overrun the key storage. + if currentNode.prefixLen >= t.maxPrefix { + break + } + currentNode = currentNode.children[k.BitValueAt(currentNode.prefixLen)] + } + } + return iter +} + +// Next produces the 'Next' key/value pair in the iteration sequence maintained by 'iter'. 'ok' is +// 'false' when the sequence ends; 'key' and 'value' are returned with empty values in that case. +func (i *descendantSPFIterator[K, T]) Next() (ok bool, key Key[K], value T) { + for i.heap.Len() > 0 { + // pop the node with the lowest prefix length from the heap + n := i.heap.popHeap() + // push the children, if any, into the heap + if n.children[0] != nil { + i.heap.pushHeap(n.children[0]) + } + if n.children[1] != nil { + i.heap.pushHeap(n.children[1]) + } + // Skip over intermediate nodes + if n.intermediate { + continue + } + return true, n.key, n.value + } + return false, key, value +} + func (t *trie[K, T]) AncestorsLongestPrefixFirst(prefixLen uint, k Key[K], fn func(prefix uint, key Key[K], value T) bool) { prefixLen = min(prefixLen, t.maxPrefix) t.treverse(prefixLen, k, func(currentNode *node[K, T]) bool { @@ -676,12 +898,23 @@ func (nodes *nodes[K, T]) Pop() any { return node } -// convenience wrappers +func (nodes *nodes[K, T]) pop() *node[K, T] { + n := len(*nodes) + node := (*nodes)[n-1] + *nodes = (*nodes)[:n-1] + return node +} + func (nodes *nodes[K, T]) push(n *node[K, T]) { + *nodes = append(*nodes, n) +} + +// convenience wrappers +func (nodes *nodes[K, T]) pushHeap(n *node[K, T]) { heap.Push(nodes, n) } -func (nodes *nodes[K, T]) pop() *node[K, T] { +func (nodes *nodes[K, T]) popHeap() *node[K, T] { return heap.Pop(nodes).(*node[K, T]) } @@ -696,11 +929,11 @@ func (n *node[K, T]) forEachShortestPrefixFirst(fn func(prefix uint, key Key[K], // has the shortest prefix length of any node in the subtree it represents. // Preallocate space for some pointers to reduce allocations and copies. nodes := make(nodes[K, T], 0, nPointersOnCacheline) - nodes.push(n) + nodes.pushHeap(n) for nodes.Len() > 0 { // pop the node with the lowest prefix length from the heap - n := nodes.pop() + n := nodes.popHeap() if !n.intermediate { if !fn(n.prefixLen, n.key, n.value) { return @@ -708,10 +941,10 @@ func (n *node[K, T]) forEachShortestPrefixFirst(fn func(prefix uint, key Key[K], } // push the children, if any, into the heap if n.children[0] != nil { - nodes.push(n.children[0]) + nodes.pushHeap(n.children[0]) } if n.children[1] != nil { - nodes.push(n.children[1]) + nodes.pushHeap(n.children[1]) } } } diff --git a/vendor/github.com/cilium/cilium/pkg/container/set/set.go b/vendor/github.com/cilium/cilium/pkg/container/set/set.go new file mode 100644 index 0000000000..5c7dab269d --- /dev/null +++ b/vendor/github.com/cilium/cilium/pkg/container/set/set.go @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Cilium + +package set + +import ( + "fmt" + "iter" + "maps" +) + +type empty struct{} + +// Set contains zero, one, or more members. Zero or one members do not consume any additional +// storage, more than one members are held in an non-exported membersMap. +type Set[T comparable] struct { + single *T + members map[T]empty +} + +// Empty returns 'true' if the set is empty. +func (s Set[T]) Empty() bool { + return s.single == nil && s.members == nil +} + +// Len returns the number of members in the set. +func (s Set[T]) Len() int { + if s.single != nil { + return 1 + } + return len(s.members) +} + +func (s Set[T]) String() string { + if s.single != nil { + return fmt.Sprintf("%v", s.single) + } + res := "" + for m := range s.members { + if res != "" { + res += "," + } + res += fmt.Sprintf("%v", m) + } + return res +} + +// NewSet returns a Set initialized to contain the members in 'members'. +func NewSet[T comparable](members ...T) Set[T] { + s := Set[T]{} + for _, member := range members { + s.Insert(member) + } + return s +} + +// Has returns 'true' if 'member' is in the set. +func (s Set[T]) Has(member T) bool { + if s.single != nil { + return *s.single == member + } + _, ok := s.members[member] + return ok +} + +// Insert inserts a member to the set. +// Returns 'true' when '*s' value has changed, +// so that if it is stored by value the caller must knows to update the stored value. +func (s *Set[T]) Insert(member T) (changed bool) { + switch s.Len() { + case 0: + s.single = &member + return true + case 1: + if member == *s.single { + return false + } + s.members = make(map[T]empty, 2) + s.members[*s.single] = empty{} + s.single = nil + s.members[member] = empty{} + return true + default: + s.members[member] = empty{} + return false + } +} + +// Merge inserts members in 'o' into to the set 's'. +// Returns 'true' when '*s' value has changed, +// so that if it is stored by value the caller must knows to update the stored value. +func (s *Set[T]) Merge(sets ...Set[T]) (changed bool) { + for _, other := range sets { + for m := range other.Members() { + changed = s.Insert(m) || changed + } + } + return changed +} + +// Remove removes a member from the set. +// Returns 'true' when '*s' value was changed, so that if it is stored by value the caller knows to +// update the stored value. +func (s *Set[T]) Remove(member T) (changed bool) { + length := s.Len() + switch length { + case 0: + case 1: + if *s.single == member { + s.single = nil + return true + } + case 2: + delete(s.members, member) + if len(s.members) == 1 { + for m := range s.members { + s.single = &m + } + s.members = nil + return true + } + default: + delete(s.members, member) + } + return false +} + +// RemoveSets removes one or more Sets from the receiver set. +// Returns 'true' when '*s' value was changed, so that if it is stored by value the caller knows to +// update the stored value. +func (s *Set[T]) RemoveSets(sets ...Set[T]) (changed bool) { + for _, other := range sets { + for m := range other.Members() { + changed = s.Remove(m) || changed + } + } + return changed +} + +// Clear makes the set '*s' empty. +func (s *Set[T]) Clear() { + s.single = nil + s.members = nil +} + +// Equal returns 'true' if the receiver and argument sets are the same. +func (s Set[T]) Equal(o Set[T]) bool { + sLen := s.Len() + oLen := o.Len() + + if sLen != oLen { + return false + } + + switch sLen { + case 0: + return true + case 1: + return *s.single == *o.single + } + // compare the elements of the maps + for member := range s.members { + if _, ok := o.members[member]; !ok { + return false + } + } + return true +} + +// Members returns an iterator for the members in the set. +func (s Set[T]) Members() iter.Seq[T] { + return func(yield func(m T) bool) { + if s.single != nil { + yield(*s.single) + } else { + for member := range s.members { + if !yield(member) { + return + } + } + } + } +} + +// MembersOfType return an iterator for each member of type M in the set. +func MembersOfType[M any, T comparable](s Set[T]) iter.Seq[M] { + return func(yield func(m M) bool) { + if s.single != nil { + if v, ok := any(*s.single).(M); ok { + yield(v) + } + } else { + for m := range s.members { + if v, ok := any(m).(M); ok { + if !yield(v) { + return + } + } + } + } + } +} + +// Get returns any one member from the set. +// Useful when it is known that the set has only one element. +func (s Set[T]) Get() (m T, found bool) { + length := s.Len() + + switch length { + case 0: + case 1: + m = *s.single + default: + for m = range s.members { + break + } + } + return m, length > 0 +} + +// Clone returns a copy of the set. +func (s Set[T]) Clone() Set[T] { + if s.members != nil { + return Set[T]{members: maps.Clone(s.members)} + } + return s // singular value or empty Set +} diff --git a/vendor/github.com/cilium/cilium/pkg/container/versioned/value.go b/vendor/github.com/cilium/cilium/pkg/container/versioned/value.go index f446845349..29c4bfe9e9 100644 --- a/vendor/github.com/cilium/cilium/pkg/container/versioned/value.go +++ b/vendor/github.com/cilium/cilium/pkg/container/versioned/value.go @@ -280,7 +280,10 @@ func (v *Coordinator) clean() { // 'keepVersion' is the current version if there are no outstanding VersionHandles keepVersion := v.version if len(v.versions) > 0 { - keepVersion = v.versions[0].version + // otherwise it is the oldest version for which there is an outstanding handle, if + // older than the current version, as if there was an implicit outstanding handle + // for the current version. + keepVersion = min(v.version, v.versions[0].version) } // Call the cleaner for 'keepVersion' only if not already called for this 'keepVersion'. diff --git a/vendor/github.com/cilium/cilium/pkg/controller/controller.go b/vendor/github.com/cilium/cilium/pkg/controller/controller.go index 6ee59316bf..0283b3247b 100644 --- a/vendor/github.com/cilium/cilium/pkg/controller/controller.go +++ b/vendor/github.com/cilium/cilium/pkg/controller/controller.go @@ -8,11 +8,11 @@ import ( "errors" "fmt" "math" + stdtime "time" "github.com/cilium/hive/cell" "github.com/sirupsen/logrus" - "github.com/cilium/cilium/pkg/inctimer" "github.com/cilium/cilium/pkg/lock" "github.com/cilium/cilium/pkg/metrics" "github.com/cilium/cilium/pkg/time" @@ -242,9 +242,6 @@ func (c *controller) GetLastErrorTimestamp() time.Time { func (c *controller) runController(params ControllerParams) { errorRetries := 1 - runTimer, timerDone := inctimer.New() - defer timerDone() - for { var err error @@ -325,7 +322,7 @@ func (c *controller) runController(params ControllerParams) { case params = <-c.update: // update channel is never closed - case <-runTimer.After(interval): + case <-stdtime.After(interval): // timer channel is not yet closed case <-c.trigger: // trigger channel is never closed diff --git a/vendor/github.com/cilium/cilium/pkg/datapath/linux/probes/managed_neighbors.go b/vendor/github.com/cilium/cilium/pkg/datapath/linux/probes/managed_neighbors.go index 596e30ab19..d80f27b97b 100644 --- a/vendor/github.com/cilium/cilium/pkg/datapath/linux/probes/managed_neighbors.go +++ b/vendor/github.com/cilium/cilium/pkg/datapath/linux/probes/managed_neighbors.go @@ -11,6 +11,7 @@ import ( "github.com/vishvananda/netlink" + "github.com/cilium/cilium/pkg/datapath/linux/safenetlink" "github.com/cilium/cilium/pkg/netns" ) @@ -70,7 +71,7 @@ func haveManagedNeighbors() (outer error) { return fmt.Errorf("failed to add neighbor: %w", err) } - nl, err := netlink.NeighList(veth.Index, 0) + nl, err := safenetlink.NeighList(veth.Index, 0) if err != nil { return fmt.Errorf("failed to list neighbors: %w", err) } diff --git a/vendor/github.com/cilium/cilium/pkg/datapath/linux/probes/probes.go b/vendor/github.com/cilium/cilium/pkg/datapath/linux/probes/probes.go index ec10584208..dcf0950dcd 100644 --- a/vendor/github.com/cilium/cilium/pkg/datapath/linux/probes/probes.go +++ b/vendor/github.com/cilium/cilium/pkg/datapath/linux/probes/probes.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "io" + "math" "net" "os" "path/filepath" @@ -392,6 +393,13 @@ func HaveFibIfindex() error { return features.HaveProgramHelper(ebpf.SchedCLS, asm.FnRedirectPeer) } +// HaveWriteableQueueMapping checks if kernel has 74e31ca850c1 ("bpf: add +// skb->queue_mapping write access from tc clsact") which is 5.1+. This got merged +// in the same kernel as the bpf_skb_ecn_set_ce() helper. +func HaveWriteableQueueMapping() error { + return features.HaveProgramHelper(ebpf.SchedCLS, asm.FnSkbEcnSetCe) +} + // HaveV2ISA is a wrapper around features.HaveV2ISA() to check if the kernel // supports the V2 ISA. // On unexpected probe results this function will terminate with log.Fatal(). @@ -462,6 +470,51 @@ var HaveTCX = sync.OnceValue(func() error { }) }) +// HaveNetkit returns nil if the running kernel supports attaching bpf programs +// to netkit devices. +var HaveNetkit = sync.OnceValue(func() error { + prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ + Type: ebpf.SchedCLS, + Instructions: asm.Instructions{ + asm.Mov.Imm(asm.R0, 0), + asm.Return(), + }, + License: "Apache-2.0", + }) + if err != nil { + return err + } + defer prog.Close() + + ns, err := netns.New() + if err != nil { + return fmt.Errorf("create netns: %w", err) + } + defer ns.Close() + + return ns.Do(func() error { + l, err := link.AttachNetkit(link.NetkitOptions{ + Program: prog, + Attach: ebpf.AttachNetkitPrimary, + Interface: math.MaxInt, + }) + // We rely on this being checked during the syscall. With + // an otherwise correct payload we expect ENODEV here as + // an indication that the feature is present. + if errors.Is(err, unix.ENODEV) { + return nil + } + if err != nil { + return fmt.Errorf("creating link: %w", err) + } + if err := l.Close(); err != nil { + return fmt.Errorf("closing link: %w", err) + } + + return fmt.Errorf("unexpected success: %w", err) + }) +}) + // HaveOuterSourceIPSupport tests whether the kernel support setting the outer // source IP address via the bpf_skb_set_tunnel_key BPF helper. We can't rely // on the verifier to reject a program using the new support because the diff --git a/vendor/github.com/cilium/cilium/pkg/datapath/linux/safenetlink/netlink_linux.go b/vendor/github.com/cilium/cilium/pkg/datapath/linux/safenetlink/netlink_linux.go new file mode 100644 index 0000000000..8b1216dd90 --- /dev/null +++ b/vendor/github.com/cilium/cilium/pkg/datapath/linux/safenetlink/netlink_linux.go @@ -0,0 +1,395 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Cilium + +package safenetlink + +import ( + "context" + "errors" + "net" + + "github.com/vishvananda/netlink" + "github.com/vishvananda/netlink/nl" + + "github.com/cilium/cilium/pkg/resiliency" + "github.com/cilium/cilium/pkg/time" +) + +const ( + netlinkRetryInterval = 1 * time.Millisecond + netlinkRetryMax = 30 +) + +// WithRetry runs the netlinkFunc. If netlinkFunc returns netlink.ErrDumpInterrupted, the function is retried. +// If success or any other error is returned, WithRetry returns immediately, propagating the error. +func WithRetry(netlinkFunc func() error) error { + return resiliency.Retry(context.Background(), netlinkRetryInterval, netlinkRetryMax, func(ctx context.Context, retries int) (bool, error) { + err := netlinkFunc() + if errors.Is(err, netlink.ErrDumpInterrupted) { + return false, nil // retry + } + + return true, err + }) +} + +// WithRetryResult works like WithRetry, but allows netlinkFunc to have a return value besides the error +func WithRetryResult[T any](netlinkFunc func() (T, error)) (out T, err error) { + err = WithRetry(func() error { + out, err = netlinkFunc() + return err + }) + return out, err +} + +// AddrList wraps netlink.AddrList, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func AddrList(link netlink.Link, family int) ([]netlink.Addr, error) { + return WithRetryResult(func() ([]netlink.Addr, error) { + return netlink.AddrList(link, family) + }) +} + +// BridgeVlanList wraps netlink.BridgeVlanList, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func BridgeVlanList() (map[int32][]*nl.BridgeVlanInfo, error) { + return WithRetryResult(func() (map[int32][]*nl.BridgeVlanInfo, error) { + return netlink.BridgeVlanList() + }) +} + +// ChainList wraps netlink.ChainList, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func ChainList(link netlink.Link, parent uint32) ([]netlink.Chain, error) { + return WithRetryResult(func() ([]netlink.Chain, error) { + return netlink.ChainList(link, parent) + }) +} + +// ClassList wraps netlink.ClassList, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func ClassList(link netlink.Link, parent uint32) ([]netlink.Class, error) { + return WithRetryResult(func() ([]netlink.Class, error) { + return netlink.ClassList(link, parent) + }) +} + +// ConntrackTableList wraps netlink.ConntrackTableList, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func ConntrackTableList(table netlink.ConntrackTableType, family netlink.InetFamily) ([]*netlink.ConntrackFlow, error) { + return WithRetryResult(func() ([]*netlink.ConntrackFlow, error) { + return netlink.ConntrackTableList(table, family) + }) +} + +// DevLinkGetDeviceList wraps netlink.DevLinkGetDeviceList, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func DevLinkGetDeviceList() ([]*netlink.DevlinkDevice, error) { + return WithRetryResult(func() ([]*netlink.DevlinkDevice, error) { + return netlink.DevLinkGetDeviceList() + }) +} + +// DevLinkGetAllPortList wraps netlink.DevLinkGetAllPortList, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func DevLinkGetAllPortList() ([]*netlink.DevlinkPort, error) { + return WithRetryResult(func() ([]*netlink.DevlinkPort, error) { + return netlink.DevLinkGetAllPortList() + }) +} + +// DevlinkGetDeviceParams wraps netlink.DevlinkGetDeviceParams, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func DevlinkGetDeviceParams(bus string, device string) ([]*netlink.DevlinkParam, error) { + return WithRetryResult(func() ([]*netlink.DevlinkParam, error) { + return netlink.DevlinkGetDeviceParams(bus, device) + }) +} + +// FilterList wraps netlink.FilterList, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func FilterList(link netlink.Link, parent uint32) ([]netlink.Filter, error) { + return WithRetryResult(func() ([]netlink.Filter, error) { + return netlink.FilterList(link, parent) + }) +} + +// FouList wraps netlink.FouList, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func FouList(fam int) ([]netlink.Fou, error) { + return WithRetryResult(func() ([]netlink.Fou, error) { + return netlink.FouList(fam) + }) +} + +// GenlFamilyList wraps netlink.GenlFamilyList, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func GenlFamilyList() ([]*netlink.GenlFamily, error) { + return WithRetryResult(func() ([]*netlink.GenlFamily, error) { + return netlink.GenlFamilyList() + }) +} + +// GTPPDPList wraps netlink.GTPPDPList, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func GTPPDPList() ([]*netlink.PDP, error) { + return WithRetryResult(func() ([]*netlink.PDP, error) { + return netlink.GTPPDPList() + }) +} + +// LinkByName wraps netlink.LinkByName, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func LinkByName(name string) (netlink.Link, error) { + return WithRetryResult(func() (netlink.Link, error) { + return netlink.LinkByName(name) + }) +} + +// LinkByAlias wraps netlink.LinkByAlias, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func LinkByAlias(alias string) (netlink.Link, error) { + return WithRetryResult(func() (netlink.Link, error) { + return netlink.LinkByAlias(alias) + }) +} + +// LinkList wraps netlink.LinkList, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func LinkList() ([]netlink.Link, error) { + return WithRetryResult(func() ([]netlink.Link, error) { + return netlink.LinkList() + }) +} + +// LinkSubscribeWithOptions wraps netlink.LinkSubscribeWithOptions, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func LinkSubscribeWithOptions(ch chan<- netlink.LinkUpdate, done <-chan struct{}, options netlink.LinkSubscribeOptions) error { + return WithRetry(func() error { + return netlink.LinkSubscribeWithOptions(ch, done, options) + }) +} + +// NeighList wraps netlink.NeighList, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func NeighList(linkIndex, family int) ([]netlink.Neigh, error) { + return WithRetryResult(func() ([]netlink.Neigh, error) { + return netlink.NeighList(linkIndex, family) + }) +} + +// NeighProxyList wraps netlink.NeighProxyList, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func NeighProxyList(linkIndex, family int) ([]netlink.Neigh, error) { + return WithRetryResult(func() ([]netlink.Neigh, error) { + return netlink.NeighProxyList(linkIndex, family) + }) +} + +// NeighListExecute wraps netlink.NeighListExecute, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func NeighListExecute(msg netlink.Ndmsg) ([]netlink.Neigh, error) { + return WithRetryResult(func() ([]netlink.Neigh, error) { + return netlink.NeighListExecute(msg) + }) +} + +// LinkGetProtinfo wraps netlink.LinkGetProtinfo, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func LinkGetProtinfo(link netlink.Link) (netlink.Protinfo, error) { + return WithRetryResult(func() (netlink.Protinfo, error) { + return netlink.LinkGetProtinfo(link) + }) +} + +// QdiscList wraps netlink.QdiscList, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func QdiscList(link netlink.Link) ([]netlink.Qdisc, error) { + return WithRetryResult(func() ([]netlink.Qdisc, error) { + return netlink.QdiscList(link) + }) +} + +// RdmaLinkList wraps netlink.RdmaLinkList, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func RdmaLinkList() ([]*netlink.RdmaLink, error) { + return WithRetryResult(func() ([]*netlink.RdmaLink, error) { + return netlink.RdmaLinkList() + }) +} + +// RdmaLinkByName wraps netlink.RdmaLinkByName, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func RdmaLinkByName(name string) (*netlink.RdmaLink, error) { + return WithRetryResult(func() (*netlink.RdmaLink, error) { + return netlink.RdmaLinkByName(name) + }) +} + +// RdmaLinkDel wraps netlink.RdmaLinkDel, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func RdmaLinkDel(name string) error { + return WithRetry(func() error { + return netlink.RdmaLinkDel(name) + }) +} + +// RouteList wraps netlink.RouteList, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func RouteList(link netlink.Link, family int) ([]netlink.Route, error) { + return WithRetryResult(func() ([]netlink.Route, error) { + return netlink.RouteList(link, family) + }) +} + +// RouteListFiltered wraps netlink.RouteListFiltered, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func RouteListFiltered(family int, filter *netlink.Route, filterMask uint64) ([]netlink.Route, error) { + return WithRetryResult(func() ([]netlink.Route, error) { + return netlink.RouteListFiltered(family, filter, filterMask) + }) +} + +// RouteListFilteredIter wraps netlink.RouteListFilteredIter, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func RouteListFilteredIter(family int, filter *netlink.Route, filterMask uint64, f func(netlink.Route) (cont bool)) error { + return WithRetry(func() error { + return netlink.RouteListFilteredIter(family, filter, filterMask, f) + }) +} + +// RouteSubscribeWithOptions wraps netlink.RouteSubscribeWithOptions, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func RouteSubscribeWithOptions(ch chan<- netlink.RouteUpdate, done <-chan struct{}, options netlink.RouteSubscribeOptions) error { + return WithRetry(func() error { + return netlink.RouteSubscribeWithOptions(ch, done, options) + }) +} + +// RuleList wraps netlink.RuleList, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func RuleList(family int) ([]netlink.Rule, error) { + return WithRetryResult(func() ([]netlink.Rule, error) { + return netlink.RuleList(family) + }) +} + +// RuleListFiltered wraps netlink.RuleListFiltered, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func RuleListFiltered(family int, filter *netlink.Rule, filterMask uint64) ([]netlink.Rule, error) { + return WithRetryResult(func() ([]netlink.Rule, error) { + return netlink.RuleListFiltered(family, filter, filterMask) + }) +} + +// SocketGet wraps netlink.SocketGet, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func SocketGet(local, remote net.Addr) (*netlink.Socket, error) { + return WithRetryResult(func() (*netlink.Socket, error) { + return netlink.SocketGet(local, remote) + }) +} + +// SocketDiagTCPInfo wraps netlink.SocketDiagTCPInfo, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func SocketDiagTCPInfo(family uint8) ([]*netlink.InetDiagTCPInfoResp, error) { + return WithRetryResult(func() ([]*netlink.InetDiagTCPInfoResp, error) { + return netlink.SocketDiagTCPInfo(family) + }) +} + +// SocketDiagTCP wraps netlink.SocketDiagTCP, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func SocketDiagTCP(family uint8) ([]*netlink.Socket, error) { + return WithRetryResult(func() ([]*netlink.Socket, error) { + return netlink.SocketDiagTCP(family) + }) +} + +// SocketDiagUDPInfo wraps netlink.SocketDiagUDPInfo, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func SocketDiagUDPInfo(family uint8) ([]*netlink.InetDiagUDPInfoResp, error) { + return WithRetryResult(func() ([]*netlink.InetDiagUDPInfoResp, error) { + return netlink.SocketDiagUDPInfo(family) + }) +} + +// SocketDiagUDP wraps netlink.SocketDiagUDP, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func SocketDiagUDP(family uint8) ([]*netlink.Socket, error) { + return WithRetryResult(func() ([]*netlink.Socket, error) { + return netlink.SocketDiagUDP(family) + }) +} + +// UnixSocketDiagInfo wraps netlink.UnixSocketDiagInfo, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func UnixSocketDiagInfo() ([]*netlink.UnixDiagInfoResp, error) { + return WithRetryResult(func() ([]*netlink.UnixDiagInfoResp, error) { + return netlink.UnixSocketDiagInfo() + }) +} + +// UnixSocketDiag wraps netlink.UnixSocketDiag, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func UnixSocketDiag() ([]*netlink.UnixSocket, error) { + return WithRetryResult(func() ([]*netlink.UnixSocket, error) { + return netlink.UnixSocketDiag() + }) +} + +// SocketXDPGetInfo wraps netlink.SocketXDPGetInfo, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func SocketXDPGetInfo(ino uint32, cookie uint64) (*netlink.XDPDiagInfoResp, error) { + return WithRetryResult(func() (*netlink.XDPDiagInfoResp, error) { + return netlink.SocketXDPGetInfo(ino, cookie) + }) +} + +// SocketDiagXDP wraps netlink.SocketDiagXDP, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func SocketDiagXDP() ([]*netlink.XDPDiagInfoResp, error) { + return WithRetryResult(func() ([]*netlink.XDPDiagInfoResp, error) { + return netlink.SocketDiagXDP() + }) +} + +// VDPAGetDevList wraps netlink.VDPAGetDevList, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func VDPAGetDevList() ([]*netlink.VDPADev, error) { + return WithRetryResult(func() ([]*netlink.VDPADev, error) { + return netlink.VDPAGetDevList() + }) +} + +// VDPAGetDevConfigList wraps netlink.VDPAGetDevConfigList, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func VDPAGetDevConfigList() ([]*netlink.VDPADevConfig, error) { + return WithRetryResult(func() ([]*netlink.VDPADevConfig, error) { + return netlink.VDPAGetDevConfigList() + }) +} + +// VDPAGetMGMTDevList wraps netlink.VDPAGetMGMTDevList, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func VDPAGetMGMTDevList() ([]*netlink.VDPAMGMTDev, error) { + return WithRetryResult(func() ([]*netlink.VDPAMGMTDev, error) { + return netlink.VDPAGetMGMTDevList() + }) +} + +// XfrmPolicyList wraps netlink.XfrmPolicyList, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func XfrmPolicyList(family int) ([]netlink.XfrmPolicy, error) { + return WithRetryResult(func() ([]netlink.XfrmPolicy, error) { + return netlink.XfrmPolicyList(family) + }) +} + +// XfrmStateList wraps netlink.XfrmStateList, but retries the call automatically +// if netlink.ErrDumpInterrupted is returned +func XfrmStateList(family int) ([]netlink.XfrmState, error) { + return WithRetryResult(func() ([]netlink.XfrmState, error) { + return netlink.XfrmStateList(family) + }) +} diff --git a/vendor/github.com/cilium/cilium/pkg/datapath/linux/safenetlink/netlink_unspecified.go b/vendor/github.com/cilium/cilium/pkg/datapath/linux/safenetlink/netlink_unspecified.go new file mode 100644 index 0000000000..046c03f99e --- /dev/null +++ b/vendor/github.com/cilium/cilium/pkg/datapath/linux/safenetlink/netlink_unspecified.go @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Cilium + +//go:build !linux + +// This file duplicates the stubs that exist in vishvananda/netlink outside the linux build. Not all +// functions defined in found in netlink_linux.go are present here, because not all have a stub in +// vishvananda/netlink, and thus some of the necessary function signature types are missing outside +// the linux build. + +package safenetlink + +import ( + "net" + + "github.com/vishvananda/netlink" +) + +func AddrList(link netlink.Link, family int) ([]netlink.Addr, error) { + return nil, netlink.ErrNotImplemented +} + +func ChainList(link netlink.Link, parent uint32) ([]netlink.Chain, error) { + return nil, netlink.ErrNotImplemented +} + +func ClassList(link netlink.Link, parent uint32) ([]netlink.Class, error) { + return nil, netlink.ErrNotImplemented +} + +func ConntrackTableList(table netlink.ConntrackTableType, family netlink.InetFamily) ([]*netlink.ConntrackFlow, error) { + return nil, netlink.ErrNotImplemented +} + +func FilterList(link netlink.Link, parent uint32) ([]netlink.Filter, error) { + return nil, netlink.ErrNotImplemented +} + +func FouList(fam int) ([]netlink.Fou, error) { + return nil, netlink.ErrNotImplemented +} + +func GenlFamilyList() ([]*netlink.GenlFamily, error) { + return nil, netlink.ErrNotImplemented +} + +func LinkByName(name string) (netlink.Link, error) { + return nil, netlink.ErrNotImplemented +} + +func LinkByAlias(alias string) (netlink.Link, error) { + return nil, netlink.ErrNotImplemented +} + +func LinkList() ([]netlink.Link, error) { + return nil, netlink.ErrNotImplemented +} + +func NeighList(linkIndex, family int) ([]netlink.Neigh, error) { + return nil, netlink.ErrNotImplemented +} + +func NeighProxyList(linkIndex, family int) ([]netlink.Neigh, error) { + return nil, netlink.ErrNotImplemented +} + +func LinkGetProtinfo(link netlink.Link) (netlink.Protinfo, error) { + return netlink.Protinfo{}, netlink.ErrNotImplemented +} + +func QdiscList(link netlink.Link) ([]netlink.Qdisc, error) { + return nil, netlink.ErrNotImplemented +} + +func RdmaLinkDel(name string) error { + return netlink.ErrNotImplemented +} + +func RouteList(link netlink.Link, family int) ([]netlink.Route, error) { + return nil, netlink.ErrNotImplemented +} + +func RouteListFiltered(family int, filter *netlink.Route, filterMask uint64) ([]netlink.Route, error) { + return nil, netlink.ErrNotImplemented +} + +func RouteListFilteredIter(family int, filter *netlink.Route, filterMask uint64, f func(netlink.Route) (cont bool)) error { + return netlink.ErrNotImplemented +} + +func RuleList(family int) ([]netlink.Rule, error) { + return nil, netlink.ErrNotImplemented +} + +func RuleListFiltered(family int, filter *netlink.Rule, filterMask uint64) ([]netlink.Rule, error) { + return nil, netlink.ErrNotImplemented +} + +func SocketGet(local, remote net.Addr) (*netlink.Socket, error) { + return nil, netlink.ErrNotImplemented +} + +func SocketDiagTCPInfo(family uint8) ([]*netlink.InetDiagTCPInfoResp, error) { + return nil, netlink.ErrNotImplemented +} + +func SocketDiagTCP(family uint8) ([]*netlink.Socket, error) { + return nil, netlink.ErrNotImplemented +} + +func SocketDiagUDPInfo(family uint8) ([]*netlink.InetDiagUDPInfoResp, error) { + return nil, netlink.ErrNotImplemented +} + +func SocketDiagUDP(family uint8) ([]*netlink.Socket, error) { + return nil, netlink.ErrNotImplemented +} + +func UnixSocketDiagInfo() ([]*netlink.UnixDiagInfoResp, error) { + return nil, netlink.ErrNotImplemented +} + +func UnixSocketDiag() ([]*netlink.UnixSocket, error) { + return nil, netlink.ErrNotImplemented +} + +func SocketXDPGetInfo(ino uint32, cookie uint64) (*netlink.XDPDiagInfoResp, error) { + return nil, netlink.ErrNotImplemented +} + +func SocketDiagXDP() ([]*netlink.XDPDiagInfoResp, error) { + return nil, netlink.ErrNotImplemented +} + +func XfrmPolicyList(family int) ([]netlink.XfrmPolicy, error) { + return nil, netlink.ErrNotImplemented +} + +func XfrmStateList(family int) ([]netlink.XfrmState, error) { + return nil, netlink.ErrNotImplemented +} diff --git a/vendor/github.com/cilium/cilium/pkg/datapath/tunnel/tunnel.go b/vendor/github.com/cilium/cilium/pkg/datapath/tunnel/tunnel.go index c914a411f8..54b2f176cd 100644 --- a/vendor/github.com/cilium/cilium/pkg/datapath/tunnel/tunnel.go +++ b/vendor/github.com/cilium/cilium/pkg/datapath/tunnel/tunnel.go @@ -9,9 +9,9 @@ import ( "github.com/cilium/hive/cell" "github.com/spf13/pflag" - "github.com/vishvananda/netlink" dpcfgdef "github.com/cilium/cilium/pkg/datapath/linux/config/defines" + "github.com/cilium/cilium/pkg/datapath/linux/safenetlink" "github.com/cilium/cilium/pkg/defaults" ) @@ -162,7 +162,7 @@ func (cfg Config) datapathConfigProvider() (dpcfgdef.NodeOut, dpcfgdef.NodeFnOut defines["TUNNEL_PORT"] = fmt.Sprintf("%d", cfg.Port()) definesFn = func() (dpcfgdef.Map, error) { - tunnelDev, err := netlink.LinkByName(cfg.DeviceName()) + tunnelDev, err := safenetlink.LinkByName(cfg.DeviceName()) if err != nil { return nil, fmt.Errorf("failed to retrieve device info for %q: %w", cfg.DeviceName(), err) } diff --git a/vendor/github.com/cilium/cilium/pkg/datapath/types/loader.go b/vendor/github.com/cilium/cilium/pkg/datapath/types/loader.go index 386fee101f..f4fafb5eec 100644 --- a/vendor/github.com/cilium/cilium/pkg/datapath/types/loader.go +++ b/vendor/github.com/cilium/cilium/pkg/datapath/types/loader.go @@ -40,7 +40,7 @@ type PreFilter interface { // Proxy is any type which installs rules related to redirecting traffic to // a proxy. type Proxy interface { - ReinstallRoutingRules() error + ReinstallRoutingRules(mtu int) error } // IptablesManager manages iptables rules. diff --git a/vendor/github.com/cilium/cilium/pkg/datapath/types/node.go b/vendor/github.com/cilium/cilium/pkg/datapath/types/node.go index ecddfa02c8..9b741a138b 100644 --- a/vendor/github.com/cilium/cilium/pkg/datapath/types/node.go +++ b/vendor/github.com/cilium/cilium/pkg/datapath/types/node.go @@ -174,6 +174,10 @@ type LocalNodeConfiguration struct { // XDPConfig holds configuration options to determine how the node should // handle XDP programs. XDPConfig xdp.Config + + // RoutingMode is the current routing mode of the local node. + // Can be 'native' or 'tunnel'. + RoutingMode string } func (cfg *LocalNodeConfiguration) DeviceNames() []string { diff --git a/vendor/github.com/cilium/cilium/pkg/datapath/types/zz_generated.deepequal.go b/vendor/github.com/cilium/cilium/pkg/datapath/types/zz_generated.deepequal.go index c0b1bc0895..f4b908812e 100644 --- a/vendor/github.com/cilium/cilium/pkg/datapath/types/zz_generated.deepequal.go +++ b/vendor/github.com/cilium/cilium/pkg/datapath/types/zz_generated.deepequal.go @@ -255,5 +255,9 @@ func (in *LocalNodeConfiguration) DeepEqual(other *LocalNodeConfiguration) bool return false } + if in.RoutingMode != other.RoutingMode { + return false + } + return true } diff --git a/vendor/github.com/cilium/cilium/pkg/defaults/defaults.go b/vendor/github.com/cilium/cilium/pkg/defaults/defaults.go index f73cfded0b..995ea59f8f 100644 --- a/vendor/github.com/cilium/cilium/pkg/defaults/defaults.go +++ b/vendor/github.com/cilium/cilium/pkg/defaults/defaults.go @@ -72,43 +72,9 @@ const ( // SockPathEnv is the environment variable to overwrite SockPath SockPathEnv = "CILIUM_SOCK" - // HubbleSockPath is the path to the UNIX domain socket exposing the Hubble - // API to clients locally. - HubbleSockPath = RuntimePath + "/hubble.sock" - - // HubbleSockPathEnv is the environment variable to overwrite - // HubbleSockPath. - HubbleSockPathEnv = "HUBBLE_SOCK" - - // HubbleRecorderStoragePath specifies the directory in which pcap files - // created via the Hubble Recorder API are stored - HubbleRecorderStoragePath = RuntimePath + "/pcaps" - - // HubbleRecorderSinkQueueSize is the queue size for each recorder sink - HubbleRecorderSinkQueueSize = 1024 - - // HubbleRedactEnabled controls if sensitive information will be redacted from L7 flows - HubbleRedactEnabled = false - - // HubbleRedactHttpURLQuery controls if the URL query will be redacted from flows - HubbleRedactHttpURLQuery = false - - // HubbleRedactHttpUserInfo controls if the user info will be redacted from flows - HubbleRedactHttpUserInfo = true - - // HubbleRedactKafkaApiKey controls if the Kafka API key will be redacted from flows - HubbleRedactKafkaApiKey = false - - // HubbleDropEventsEnabled controls whether Hubble should create v1.Events - // for packet drops related to pods - HubbleDropEventsEnabled = false - - // HubbleDropEventsInterval controls the minimum time between emitting events - // with the same source and destination IP - HubbleDropEventsInterval = 2 * time.Minute - - // HubbleDropEventsReasons controls which drop reasons to emit events for - HubbleDropEventsReasons = "auth_required,policy_denied" + // ShellSockPath is the path to the UNIX domain socket exposing the debug shell + // to which "cilium-dbg shell" connects to. + ShellSockPath = RuntimePath + "/shell.sock" // MonitorSockPath1_2 is the path to the UNIX domain socket used to // distribute BPF and agent events to listeners. @@ -571,6 +537,13 @@ const ( // EnableK8sNetworkPolicy enables support for K8s NetworkPolicy. EnableK8sNetworkPolicy = true + // EnableCiliumNetworkPolicy enables support for Cilium Network Policy. + EnableCiliumNetworkPolicy = true + + // EnableCiliumClusterwideNetworkPolicy enables support for Cilium Clusterwide + // Network Policy. + EnableCiliumClusterwideNetworkPolicy = true + // MaxConnectedClusters sets the maximum number of clusters that can be // connected in a clustermesh. // The value is used to determine the bit allocation for cluster ID and @@ -590,8 +563,8 @@ const ( // BPFEventsTraceEnabled controls whether the Cilium datapath exposes "trace" events to Cilium monitor and Hubble. BPFEventsTraceEnabled = true - // BPFConntrackAccountingEnabled controls whether CT accounting for packets and bytes is enabled - BPFConntrackAccountingEnabled = false + // BPFConntrackAccounting controls whether CT accounting for packets and bytes is enabled + BPFConntrackAccounting = false // EnableEnvoyConfig is the default value for option.EnableEnvoyConfig EnableEnvoyConfig = false @@ -604,6 +577,9 @@ const ( // EnableNonDefaultDenyPolicies allows policies to define whether they are operating in default-deny mode EnableNonDefaultDenyPolicies = true + + // EnableSourceIPVerification is the default value for source ip validation + EnableSourceIPVerification = true ) var ( diff --git a/vendor/github.com/cilium/cilium/pkg/health/client/client.go b/vendor/github.com/cilium/cilium/pkg/health/client/client.go index 2867cfaaa6..c30e1b25f4 100644 --- a/vendor/github.com/cilium/cilium/pkg/health/client/client.go +++ b/vendor/github.com/cilium/cilium/pkg/health/client/client.go @@ -320,63 +320,88 @@ func GetAllEndpointAddresses(node *models.NodeStatus) []*models.PathStatus { return append([]*models.PathStatus{node.HealthEndpoint.PrimaryAddress}, node.HealthEndpoint.SecondaryAddresses...) } -func formatNodeStatus(w io.Writer, node *models.NodeStatus, printAll, succinct, verbose, localhost bool) { +func formatNodeStatus(w io.Writer, node *models.NodeStatus, allNodes, verbose, localhost bool) bool { localStr := "" if localhost { localStr = " (localhost)" } - if succinct { - if printAll || !nodeIsHealthy(node) { - ips := []string{getPrimaryAddressIP(node)} - for _, addr := range GetHostSecondaryAddresses(node) { - if addr == nil { - continue - } - ips = append(ips, addr.IP) - } - hostStatuses := SummarizePathConnectivityStatusType(GetAllHostAddresses(node)) - endpointStatuses := SummarizePathConnectivityStatusType(GetAllEndpointAddresses(node)) - fmt.Fprintf(w, " %s%s\t%s\t%d/%d", node.Name, localStr, strings.Join(ips, ","), hostStatuses[ConnStatusReachable], len(GetAllHostAddresses(node))) - if hostStatuses[ConnStatusUnknown] > 0 { - fmt.Fprintf(w, " (%d unknown)", hostStatuses[ConnStatusUnknown]) - } - fmt.Fprintf(w, "\t%d/%d", endpointStatuses[ConnStatusReachable], len(GetAllEndpointAddresses(node))) - if endpointStatuses[ConnStatusUnknown] > 0 { - fmt.Fprintf(w, " (%d unknown)", endpointStatuses[ConnStatusUnknown]) - } - fmt.Fprintf(w, "\n") - } - } else { + if verbose { fmt.Fprintf(w, " %s%s:\n", node.Name, localStr) formatPathStatus(w, "Host", GetHostPrimaryAddress(node), " ", verbose) unhealthyPaths := !allPathsAreHealthyOrUnknown(GetHostSecondaryAddresses(node)) if (verbose || unhealthyPaths) && node.Host != nil { for _, addr := range node.Host.SecondaryAddresses { - formatPathStatus(w, "Secondary", addr, " ", verbose) + formatPathStatus(w, "Secondary Host", addr, " ", verbose) } } formatPathStatus(w, "Endpoint", GetEndpointPrimaryAddress(node), " ", verbose) unhealthyPaths = !allPathsAreHealthyOrUnknown(GetEndpointSecondaryAddresses(node)) if (verbose || unhealthyPaths) && node.HealthEndpoint != nil { for _, addr := range node.HealthEndpoint.SecondaryAddresses { - formatPathStatus(w, "Secondary", addr, " ", verbose) + formatPathStatus(w, "Secondary Endpoint", addr, " ", verbose) + } + } + return true + } + + hostStatuses := SummarizePathConnectivityStatusType(GetAllHostAddresses(node)) + endpointStatuses := SummarizePathConnectivityStatusType(GetAllEndpointAddresses(node)) + + if !nodeIsHealthy(node) { + ips := []string{getPrimaryAddressIP(node)} + for _, addr := range GetHostSecondaryAddresses(node) { + if addr == nil { + continue + } + ips = append(ips, addr.IP) + } + fmt.Fprintf(w, " %s%s\t%s\t%d/%d", node.Name, localStr, strings.Join(ips, ","), hostStatuses[ConnStatusReachable], len(GetAllHostAddresses(node))) + if hostStatuses[ConnStatusUnknown] > 0 { + fmt.Fprintf(w, " (%d unknown)", hostStatuses[ConnStatusUnknown]) + } + fmt.Fprintf(w, "\t%d/%d", endpointStatuses[ConnStatusReachable], len(GetAllEndpointAddresses(node))) + if endpointStatuses[ConnStatusUnknown] > 0 { + fmt.Fprintf(w, " (%d unknown)", endpointStatuses[ConnStatusUnknown]) + } + fmt.Fprintf(w, "\n") + return true + } + + if allNodes { + ips := []string{getPrimaryAddressIP(node)} + for _, addr := range GetHostSecondaryAddresses(node) { + if addr == nil { + continue } + ips = append(ips, addr.IP) + } + fmt.Fprintf(w, " %s%s\t%s\t%d/%d", node.Name, localStr, strings.Join(ips, ","), hostStatuses[ConnStatusReachable], len(GetAllHostAddresses(node))) + if hostStatuses[ConnStatusUnknown] > 0 { + fmt.Fprintf(w, " (%d unknown)", hostStatuses[ConnStatusUnknown]) } + fmt.Fprintf(w, "\t%d/%d", endpointStatuses[ConnStatusReachable], len(GetAllEndpointAddresses(node))) + if endpointStatuses[ConnStatusUnknown] > 0 { + fmt.Fprintf(w, " (%d unknown)", endpointStatuses[ConnStatusUnknown]) + } + fmt.Fprintf(w, "\n") + return true } + + return false } // FormatHealthStatusResponse writes a HealthStatusResponse as a string to the // writer. // -// 'printAll', if true, causes all nodes to be printed regardless of status -// 'succinct', if true, causes node health to be output as one line per node -// 'verbose', if true, overrides 'succinct' and prints all information +// 'allNodes', if true, causes all nodes to be printed regardless of status +// 'verbose', if true, prints all information // 'maxLines', if nonzero, determines the maximum number of lines to print -func FormatHealthStatusResponse(w io.Writer, sr *models.HealthStatusResponse, printAll, succinct, verbose bool, maxLines int) { +func FormatHealthStatusResponse(w io.Writer, sr *models.HealthStatusResponse, allNodes bool, verbose bool, maxLines int) { var ( - healthy int - localhost *models.NodeStatus + healthy int + localhost *models.NodeStatus + printedLines int ) for _, node := range sr.Nodes { if nodeIsHealthy(node) { @@ -386,37 +411,35 @@ func FormatHealthStatusResponse(w io.Writer, sr *models.HealthStatusResponse, pr localhost = node } } - if succinct { - fmt.Fprintf(w, "Cluster health:\t%d/%d reachable\t(%s)\n", - healthy, len(sr.Nodes), sr.Timestamp) - if printAll || healthy < len(sr.Nodes) { - fmt.Fprintf(w, " Name\tIP\tNode\tEndpoints\n") - } - } else { - fmt.Fprintf(w, "Probe time:\t%s\n", sr.Timestamp) - fmt.Fprintf(w, "Nodes:\n") - } + + fmt.Fprintf(w, "Cluster health:\t%d/%d reachable\t(%s)\n", + healthy, len(sr.Nodes), sr.Timestamp) + + fmt.Fprintf(w, "Name\tIP\tNode\tEndpoints\n") if localhost != nil { - formatNodeStatus(w, localhost, printAll, succinct, verbose, true) - maxLines-- + if formatNodeStatus(w, localhost, allNodes, verbose, true) { + printedLines++ + } } nodes := sr.Nodes sort.Slice(nodes, func(i, j int) bool { return strings.Compare(nodes[i].Name, nodes[j].Name) < 0 }) - for n, node := range nodes { - if maxLines > 0 && n > maxLines { + for _, node := range nodes { + if printedLines == maxLines { break } if node == localhost { continue } - formatNodeStatus(w, node, printAll, succinct, verbose, false) + if formatNodeStatus(w, node, allNodes, verbose, false) { + printedLines++ + } } - if maxLines > 0 && len(sr.Nodes)-healthy > maxLines { - fmt.Fprintf(w, " ...") + if len(sr.Nodes)-printedLines-healthy > 0 { + fmt.Fprintf(w, " ...\n") } } @@ -424,9 +447,9 @@ func FormatHealthStatusResponse(w io.Writer, sr *models.HealthStatusResponse, pr // daemon via the default channel and formats its output as a string to the // writer. // -// 'succinct', 'verbose' and 'maxLines' are handled the same as in +// 'verbose' and 'maxLines' are handled the same as in // FormatHealthStatusResponse(). -func GetAndFormatHealthStatus(w io.Writer, succinct, verbose bool, maxLines int) { +func GetAndFormatHealthStatus(w io.Writer, allNodes bool, verbose bool, maxLines int) { client, err := NewClient("") if err != nil { fmt.Fprintf(w, "Cluster health:\t\t\tClient error: %s\n", err) @@ -438,5 +461,5 @@ func GetAndFormatHealthStatus(w io.Writer, succinct, verbose bool, maxLines int) fmt.Fprintf(w, "Cluster health:\t\t\tWarning\tcilium-health daemon unreachable\n") return } - FormatHealthStatusResponse(w, hr.Payload, verbose, succinct, verbose, maxLines) + FormatHealthStatusResponse(w, hr.Payload, allNodes, verbose, maxLines) } diff --git a/vendor/github.com/cilium/cilium/pkg/hive/feature_lifecycle.go b/vendor/github.com/cilium/cilium/pkg/hive/feature_lifecycle.go deleted file mode 100644 index 2b95718301..0000000000 --- a/vendor/github.com/cilium/cilium/pkg/hive/feature_lifecycle.go +++ /dev/null @@ -1,128 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of Cilium - -package hive - -import ( - "context" - "errors" - "fmt" - "log/slog" - "maps" - "slices" - - "github.com/cilium/hive/cell" - - "github.com/cilium/cilium/pkg/lock" -) - -type FeatureLifecycleInterface interface { - Append(Feature, cell.Hook) error - Start(Feature, context.Context, *slog.Logger) error - Stop(Feature, context.Context, *slog.Logger) error - - IsRunning(Feature) bool - List() []Feature -} - -type Feature string -type FeatureLifecycle struct { - mu lock.Mutex - hooks map[Feature][]cell.Hook - status map[Feature]bool -} - -func NewFeatureLifecycle() *FeatureLifecycle { - return &FeatureLifecycle{ - hooks: make(map[Feature][]cell.Hook), - status: make(map[Feature]bool), - } -} - -// Append adds a hook to the feature hooks, marking the feature as not running. -// It returns an error if the feature is already running. -func (fl *FeatureLifecycle) Append(f Feature, h cell.Hook) error { - fl.mu.Lock() - defer fl.mu.Unlock() - - if status, ok := fl.status[f]; ok && status { - return fmt.Errorf("cannot add hooks to a running feature: %s", f) - } - - fl.hooks[f] = append(fl.hooks[f], h) - fl.status[f] = false - - return nil -} - -// Start attempts to start a feature by executing its associated hooks. -// It returns an error if the feature is already running -// or if any hook fails to start. -func (fl *FeatureLifecycle) Start(f Feature, c context.Context, l *slog.Logger) error { - fl.mu.Lock() - defer fl.mu.Unlock() - - if status, ok := fl.status[f]; ok && status { - return fmt.Errorf("feature %s is already running", f) - } - - l.Debug("Starting hooks for", "feature", f) - for _, hook := range fl.hooks[f] { - if err := hook.Start(c); err != nil { - l.Error("Start hook failed", "error", err) - return fmt.Errorf("starting hook for feature %s: %w", f, err) - } - } - - fl.status[f] = true - - return nil -} - -// Stop attempts to stop a feature by stopping its associated hooks. -// It returns an error if the feature is already stopped. -// If any hook encounters an error during stopping it aggregates into return error -func (fl *FeatureLifecycle) Stop(f Feature, c context.Context, l *slog.Logger) error { - fl.mu.Lock() - defer fl.mu.Unlock() - - if status, ok := fl.status[f]; ok && !status { - return fmt.Errorf("feature %s is already stopped", f) - } - - var errs error - - l.Debug("Stopping hooks for", "feature", f) - - for i := len(fl.hooks[f]) - 1; i >= 0; i-- { - hook := fl.hooks[f][i] - if err := hook.Stop(c); err != nil { - l.Error("Stop hook failed", "error", err) - errs = errors.Join(errs, err) - } - } - - fl.status[f] = false - - return errs -} - -// IsRunning checks if a feature is currently running. -// It returns true if the feature exists in status map -// and its status is true, and false otherwise. -func (fl *FeatureLifecycle) IsRunning(f Feature) bool { - fl.mu.Lock() - defer fl.mu.Unlock() - - status, ok := fl.status[f] - return ok && status - -} - -// List returns a list of all features registered -func (fl *FeatureLifecycle) List() []Feature { - fl.mu.Lock() - defer fl.mu.Unlock() - - return slices.Collect(maps.Keys(fl.hooks)) -} diff --git a/vendor/github.com/cilium/cilium/pkg/hive/health/metrics.go b/vendor/github.com/cilium/cilium/pkg/hive/health/metrics.go index b1f24738f4..1e65a15dce 100644 --- a/vendor/github.com/cilium/cilium/pkg/hive/health/metrics.go +++ b/vendor/github.com/cilium/cilium/pkg/hive/health/metrics.go @@ -18,7 +18,8 @@ import ( ) type Metrics struct { - HealthStatusGauge metric.Vec[metric.Gauge] + HealthStatusGauge metric.Vec[metric.Gauge] + DegradedHealthStatusGauge metric.DeletableVec[metric.Gauge] } func newMetrics() *Metrics { @@ -30,10 +31,18 @@ func newMetrics() *Metrics { Name: "status", Help: "Counts of health status levels of Hive components", }, []string{"status"}), + + DegradedHealthStatusGauge: metric.NewGaugeVec(metric.GaugeOpts{ + Namespace: "cilium", + Subsystem: "hive", + Name: "degraded_status", + Help: "Counts degraded health status levels of Hive components labeled by modules", + Disabled: true, + }, []string{"module"}), } } -type publishFunc func(map[types.Level]uint64) +type publishFunc func(map[types.Level]uint64, map[string]uint64) type metricPublisherParams struct { cell.In @@ -44,13 +53,24 @@ type metricPublisherParams struct { Metrics *Metrics } -// metricPublisher periodically publishes the hive module health metric (hive_health_status_levels). +// metricPublisher periodically publishes the hive module health metric +// * cilium_hive_status +// * cilium_hive_degraded_status func metricPublisher(p metricPublisherParams) { // Performs the actual writing to the metric. Extracted to make testing easy. - publish := func(stats map[types.Level]uint64) { + publish := func(stats map[types.Level]uint64, degradedModuleCount map[string]uint64) { for l, v := range stats { p.Metrics.HealthStatusGauge.WithLabelValues(strings.ToLower(string(l))).Set(float64(v)) } + for k, v := range degradedModuleCount { + // If the module is healthy attempt to remove any associated metrics with that module + if v == 0 { + p.Metrics.DegradedHealthStatusGauge.DeleteLabelValues(k) + continue + } + + p.Metrics.DegradedHealthStatusGauge.WithLabelValues(k).Set(float64(v)) + } } if p.Metrics.HealthStatusGauge.IsEnabled() { @@ -68,13 +88,34 @@ func publishJob(ctx context.Context, p metricPublisherParams, publish publishFun limiter := rate.NewLimiter(15*time.Second, 3) defer limiter.Stop() // Avoids leaking a goroutine. + idToStatus := make(map[string]uint64) it, watch := p.Table.AllWatch(p.DB.ReadTxn()) for { stats := make(map[types.Level]uint64) + // Reset health ID status counts + for k := range idToStatus { + idToStatus[k] = 0 + } + for obj := range it { stats[obj.Level]++ + + _, ok := idToStatus[obj.ID.Module.String()] + if obj.Level == types.LevelDegraded { + idToStatus[obj.ID.Module.String()]++ + } else if !ok { + idToStatus[obj.ID.Module.String()] = 0 + } + } + + publish(stats, idToStatus) + + // Removed old IDs + for k, v := range idToStatus { + if v == 0 { + delete(idToStatus, k) + } } - publish(stats) select { case <-ctx.Done(): diff --git a/vendor/github.com/cilium/cilium/pkg/hive/hive.go b/vendor/github.com/cilium/cilium/pkg/hive/hive.go index ffeb583cc4..82397c860d 100644 --- a/vendor/github.com/cilium/cilium/pkg/hive/hive.go +++ b/vendor/github.com/cilium/cilium/pkg/hive/hive.go @@ -16,11 +16,14 @@ import ( "github.com/cilium/statedb" "github.com/sirupsen/logrus" + flowpb "github.com/cilium/cilium/api/v1/flow" "github.com/cilium/cilium/pkg/cidr" "github.com/cilium/cilium/pkg/hive/health" "github.com/cilium/cilium/pkg/hive/health/types" + "github.com/cilium/cilium/pkg/hubble" "github.com/cilium/cilium/pkg/logging" "github.com/cilium/cilium/pkg/logging/logfields" + "github.com/cilium/cilium/pkg/metrics" ) type ( @@ -40,18 +43,36 @@ func New(cells ...cell.Cell) *Hive { cells = append( slices.Clone(cells), - health.Cell, job.Cell, - statedb.Cell, + // Module health + cell.Group( + health.Cell, + cell.Provide( + func(provider types.Provider) cell.Health { + return provider.ForModule(nil) + }, + ), + ), + + // StateDB and its metrics + cell.Group( + statedb.Cell, + + metrics.Metric(NewStateDBMetrics), + metrics.Metric(NewStateDBReconcilerMetrics), + cell.Provide( + NewStateDBMetricsImpl, + NewStateDBReconcilerMetricsImpl, + ), + ), + + // The root logrus FieldLogger. cell.Provide( - NewStateDBMetrics, - NewStateDBReconcilerMetrics, func() logrus.FieldLogger { return logging.DefaultLogger }, - func(provider types.Provider) cell.Health { - return provider.ForModule(nil) - }, - )) + ), + ) + // Scope logging and health by module ID. moduleDecorators := []cell.ModuleDecorator{ func(log logrus.FieldLogger, mid cell.ModuleID) logrus.FieldLogger { @@ -93,6 +114,20 @@ var decodeHooks = cell.DecodeHooks{ } return cidr.ParseCIDR(s) }, + // Decode JSON encoded *flowpb.FlowFilter fields + func(from reflect.Type, to reflect.Type, data interface{}) (interface{}, error) { + if from.Kind() != reflect.Slice { + return data, nil + } + xs, ok := data.([]string) + if !ok { + return data, nil + } + if to != reflect.TypeOf(([]*flowpb.FlowFilter)(nil)) { + return data, nil + } + return hubble.ParseFlowFilters(xs...) + }, } func AddConfigOverride[Cfg cell.Flagger](h *Hive, override func(*Cfg)) { diff --git a/vendor/github.com/cilium/cilium/pkg/hive/reconciler_metrics.go b/vendor/github.com/cilium/cilium/pkg/hive/reconciler_metrics.go index f58b3ae069..9101d2eb82 100644 --- a/vendor/github.com/cilium/cilium/pkg/hive/reconciler_metrics.go +++ b/vendor/github.com/cilium/cilium/pkg/hive/reconciler_metrics.go @@ -25,76 +25,73 @@ type ReconcilerMetrics struct { } const ( - LabelModuleId = "module_id" - LabelOperation = "op" + labelModuleId = "module_id" + labelOperation = "op" ) -func NewStateDBReconcilerMetrics() (ReconcilerMetrics, reconciler.Metrics) { +func NewStateDBReconcilerMetrics() ReconcilerMetrics { m := ReconcilerMetrics{ ReconciliationCount: metric.NewCounterVec(metric.CounterOpts{ - ConfigName: metrics.Namespace + "_count", - Disabled: true, - Namespace: metrics.Namespace, - Subsystem: "reconciler", - Name: "count", - Help: "Number of reconciliation rounds performed", - }, []string{LabelModuleId}), + Disabled: true, + Namespace: metrics.Namespace, + Subsystem: "reconciler", + Name: "count", + Help: "Number of reconciliation rounds performed", + }, []string{labelModuleId}), ReconciliationDuration: metric.NewHistogramVec(metric.HistogramOpts{ - ConfigName: metrics.Namespace + "_reconciler_duration_seconds", - Disabled: true, - Namespace: metrics.Namespace, - Subsystem: "reconciler", - Name: "duration_seconds", - Help: "Histogram of per-operation duration during reconciliation", - }, []string{LabelModuleId, LabelOperation}), + Disabled: true, + Namespace: metrics.Namespace, + Subsystem: "reconciler", + Name: "duration_seconds", + Help: "Histogram of per-operation duration during reconciliation", + }, []string{labelModuleId, labelOperation}), ReconciliationTotalErrors: metric.NewCounterVec(metric.CounterOpts{ - ConfigName: metrics.Namespace + "_reconciler_errors_total", - Disabled: true, - Namespace: metrics.Namespace, - Subsystem: "reconciler", - Name: "errors_total", - Help: "Total number of errors encountered during reconciliation", - }, []string{LabelModuleId}), + Disabled: true, + Namespace: metrics.Namespace, + Subsystem: "reconciler", + Name: "errors_total", + Help: "Total number of errors encountered during reconciliation", + }, []string{labelModuleId}), ReconciliationCurrentErrors: metric.NewGaugeVec(metric.GaugeOpts{ - ConfigName: metrics.Namespace + "_reconciler_errors_current", - Disabled: true, - Namespace: metrics.Namespace, - Subsystem: "reconciler", - Name: "errors_current", - Help: "The number of objects currently failing to be reconciled", - }, []string{LabelModuleId}), + Disabled: true, + Namespace: metrics.Namespace, + Subsystem: "reconciler", + Name: "errors_current", + Help: "The number of objects currently failing to be reconciled", + }, []string{labelModuleId}), PruneCount: metric.NewCounterVec(metric.CounterOpts{ - ConfigName: metrics.Namespace + "_reconciler_prune_count", - Disabled: true, - Namespace: metrics.Namespace, - Subsystem: "reconciler", - Name: "prune_count", - Help: "Number of prunes performed", - }, []string{LabelModuleId}), + Disabled: true, + Namespace: metrics.Namespace, + Subsystem: "reconciler", + Name: "prune_count", + Help: "Number of prunes performed", + }, []string{labelModuleId}), PruneTotalErrors: metric.NewCounterVec(metric.CounterOpts{ - ConfigName: metrics.Namespace + "_reconciler_prune_errors_total", - Disabled: true, - Namespace: metrics.Namespace, - Subsystem: "reconciler", - Name: "full_errors_total", - Help: "Total number of errors encountered during full reconciliation", - }, []string{LabelModuleId}), + Disabled: true, + Namespace: metrics.Namespace, + Subsystem: "reconciler", + Name: "prune_errors_total", + Help: "Total number of errors encountered during pruning", + }, []string{labelModuleId}), PruneDuration: metric.NewHistogramVec(metric.HistogramOpts{ - ConfigName: metrics.Namespace + "_reconciler_prune_duration_seconds", - Disabled: true, - Namespace: metrics.Namespace, - Subsystem: "reconciler", - Name: "full_duration_seconds", - Help: "Histogram of per-operation duration during full reconciliation", - }, []string{LabelModuleId, LabelOperation}), + Disabled: true, + Namespace: metrics.Namespace, + Subsystem: "reconciler", + Name: "prune_duration_seconds", + Help: "Histogram of pruning duration", + }, []string{labelModuleId}), } - return m, &reconcilerMetricsImpl{m} + return m +} + +func NewStateDBReconcilerMetricsImpl(m ReconcilerMetrics) reconciler.Metrics { + return &reconcilerMetricsImpl{m} } type reconcilerMetricsImpl struct { @@ -104,7 +101,7 @@ type reconcilerMetricsImpl struct { // PruneDuration implements reconciler.Metrics. func (m *reconcilerMetricsImpl) PruneDuration(moduleID cell.FullModuleID, duration time.Duration) { if m.m.PruneDuration.IsEnabled() { - m.m.PruneDuration.WithLabelValues(LabelModuleId, moduleID.String()). + m.m.PruneDuration.WithLabelValues(moduleID.String()). Observe(duration.Seconds()) } } @@ -112,9 +109,9 @@ func (m *reconcilerMetricsImpl) PruneDuration(moduleID cell.FullModuleID, durati // FullReconciliationErrors implements reconciler.Metrics. func (m *reconcilerMetricsImpl) PruneError(moduleID cell.FullModuleID, err error) { if m.m.PruneCount.IsEnabled() { - m.m.PruneCount.WithLabelValues(LabelModuleId, moduleID.String()) + m.m.PruneCount.WithLabelValues(moduleID.String()).Inc() } - if m.m.PruneTotalErrors.IsEnabled() { + if err != nil && m.m.PruneTotalErrors.IsEnabled() { m.m.PruneTotalErrors.WithLabelValues(moduleID.String()).Add(1) } } @@ -122,10 +119,10 @@ func (m *reconcilerMetricsImpl) PruneError(moduleID cell.FullModuleID, err error // ReconciliationDuration implements reconciler.Metrics. func (m *reconcilerMetricsImpl) ReconciliationDuration(moduleID cell.FullModuleID, operation string, duration time.Duration) { if m.m.ReconciliationCount.IsEnabled() { - m.m.ReconciliationCount.WithLabelValues(LabelModuleId, moduleID.String()).Inc() + m.m.ReconciliationCount.WithLabelValues(moduleID.String()).Inc() } if m.m.ReconciliationDuration.IsEnabled() { - m.m.ReconciliationDuration.WithLabelValues(LabelModuleId, moduleID.String(), LabelOperation, operation). + m.m.ReconciliationDuration.WithLabelValues(moduleID.String(), operation). Observe(duration.Seconds()) } } @@ -133,10 +130,10 @@ func (m *reconcilerMetricsImpl) ReconciliationDuration(moduleID cell.FullModuleI // ReconciliationErrors implements reconciler.Metrics. func (m *reconcilerMetricsImpl) ReconciliationErrors(moduleID cell.FullModuleID, new, current int) { if m.m.ReconciliationCurrentErrors.IsEnabled() { - m.m.ReconciliationCurrentErrors.WithLabelValues(LabelModuleId, moduleID.String()).Set(float64(current)) + m.m.ReconciliationCurrentErrors.WithLabelValues(moduleID.String()).Set(float64(current)) } if m.m.ReconciliationTotalErrors.IsEnabled() { - m.m.ReconciliationCurrentErrors.WithLabelValues(LabelModuleId, moduleID.String()).Add(float64(new)) + m.m.ReconciliationCurrentErrors.WithLabelValues(moduleID.String()).Add(float64(new)) } } diff --git a/vendor/github.com/cilium/cilium/pkg/hive/statedb_metrics.go b/vendor/github.com/cilium/cilium/pkg/hive/statedb_metrics.go index a9e86ffb1b..3e764604f9 100644 --- a/vendor/github.com/cilium/cilium/pkg/hive/statedb_metrics.go +++ b/vendor/github.com/cilium/cilium/pkg/hive/statedb_metrics.go @@ -34,6 +34,12 @@ type StateDBMetrics struct { TableGraveyardCleaningDuration metric.Vec[metric.Observer] } +const ( + labelTable = "table" + labelTables = "tables" + labelHandle = "handle" +) + type stateDBMetricsImpl struct { m StateDBMetrics } @@ -41,42 +47,42 @@ type stateDBMetricsImpl struct { // DeleteTrackerCount implements statedb.Metrics. func (i stateDBMetricsImpl) DeleteTrackerCount(tableName string, numTrackers int) { if i.m.TableDeleteTrackerCount.IsEnabled() { - i.m.TableDeleteTrackerCount.WithLabelValues("table", tableName).Set(float64(numTrackers)) + i.m.TableDeleteTrackerCount.WithLabelValues(tableName).Set(float64(numTrackers)) } } // GraveyardCleaningDuration implements statedb.Metrics. func (i stateDBMetricsImpl) GraveyardCleaningDuration(tableName string, duration time.Duration) { if i.m.TableGraveyardCleaningDuration.IsEnabled() { - i.m.TableGraveyardCleaningDuration.WithLabelValues("table", tableName).Observe(float64(duration.Seconds())) + i.m.TableGraveyardCleaningDuration.WithLabelValues(tableName).Observe(float64(duration.Seconds())) } } // GraveyardLowWatermark implements statedb.Metrics. func (i stateDBMetricsImpl) GraveyardLowWatermark(tableName string, lowWatermark uint64) { if i.m.TableGraveyardLowWatermark.IsEnabled() { - i.m.TableGraveyardLowWatermark.WithLabelValues("table", tableName).Set(float64(lowWatermark)) + i.m.TableGraveyardLowWatermark.WithLabelValues(tableName).Set(float64(lowWatermark)) } } // GraveyardObjectCount implements statedb.Metrics. func (i stateDBMetricsImpl) GraveyardObjectCount(tableName string, numDeletedObjects int) { if i.m.TableGraveyardObjectCount.IsEnabled() { - i.m.TableGraveyardObjectCount.WithLabelValues("table", tableName).Set(float64(numDeletedObjects)) + i.m.TableGraveyardObjectCount.WithLabelValues(tableName).Set(float64(numDeletedObjects)) } } // ObjectCount implements statedb.Metrics. func (i stateDBMetricsImpl) ObjectCount(tableName string, numObjects int) { if i.m.TableObjectCount.IsEnabled() { - i.m.TableObjectCount.WithLabelValues("table", tableName).Set(float64(numObjects)) + i.m.TableObjectCount.WithLabelValues(tableName).Set(float64(numObjects)) } } // Revision implements statedb.Metrics. func (i stateDBMetricsImpl) Revision(tableName string, revision uint64) { if i.m.TableRevision.IsEnabled() { - i.m.TableRevision.WithLabelValues("table", tableName).Set(float64(revision)) + i.m.TableRevision.WithLabelValues(tableName).Set(float64(revision)) } } @@ -84,8 +90,7 @@ func (i stateDBMetricsImpl) Revision(tableName string, revision uint64) { func (i stateDBMetricsImpl) WriteTxnDuration(handle string, tables []string, acquire time.Duration) { if i.m.WriteTxnDuration.IsEnabled() { i.m.WriteTxnDuration.WithLabelValues( - "handle", handle, - "tables", strings.Join(tables, ","), + handle, strings.Join(tables, ","), ).Observe(acquire.Seconds()) } } @@ -93,10 +98,7 @@ func (i stateDBMetricsImpl) WriteTxnDuration(handle string, tables []string, acq // WriteTxnTableAcquisition implements statedb.Metrics. func (i stateDBMetricsImpl) WriteTxnTableAcquisition(handle string, tableName string, acquire time.Duration) { if i.m.TableContention.IsEnabled() { - i.m.TableContention.WithLabelValues( - "handle", handle, - "table", tableName, - ) + i.m.TableContention.WithLabelValues(handle, tableName) } } @@ -104,79 +106,82 @@ func (i stateDBMetricsImpl) WriteTxnTableAcquisition(handle string, tableName st func (i stateDBMetricsImpl) WriteTxnTotalAcquisition(handle string, tables []string, acquire time.Duration) { if i.m.WriteTxnAcquisition.IsEnabled() { i.m.WriteTxnAcquisition.WithLabelValues( - "handle", handle, - "tables", strings.Join(tables, ","), + handle, strings.Join(tables, ","), ) } } var _ statedb.Metrics = stateDBMetricsImpl{} -func NewStateDBMetrics() (StateDBMetrics, statedb.Metrics) { +func NewStateDBMetrics() StateDBMetrics { m := StateDBMetrics{ WriteTxnDuration: metric.NewHistogramVec(metric.HistogramOpts{ - Namespace: metrics.CiliumAgentNamespace, + Namespace: metrics.Namespace, Subsystem: "statedb", Name: "write_txn_duration_seconds", Help: "How long a write transaction was held.", Disabled: true, - }, []string{"tables", "handle"}), + }, []string{labelHandle, labelTables}), WriteTxnAcquisition: metric.NewHistogramVec(metric.HistogramOpts{ - Namespace: metrics.CiliumAgentNamespace, + Namespace: metrics.Namespace, Subsystem: "statedb", Name: "write_txn_acquisition_seconds", Help: "How long it took to acquire a write transaction for all tables.", Disabled: true, - }, []string{"tables", "handle"}), + }, []string{labelHandle, labelTables}), TableContention: metric.NewGaugeVec(metric.GaugeOpts{ - Namespace: metrics.CiliumAgentNamespace, + Namespace: metrics.Namespace, Subsystem: "statedb", Name: "table_contention_seconds", Help: "How long writers were blocked while waiting to acquire a write transaction for a specific table.", Disabled: true, - }, []string{"table"}), + }, []string{labelHandle, labelTable}), TableObjectCount: metric.NewGaugeVec(metric.GaugeOpts{ - Namespace: metrics.CiliumAgentNamespace, + Namespace: metrics.Namespace, Subsystem: "statedb", Name: "table_objects", Help: "The amount of objects in a given table.", Disabled: true, - }, []string{"table"}), + }, []string{labelTable}), TableRevision: metric.NewGaugeVec(metric.GaugeOpts{ - Namespace: metrics.CiliumAgentNamespace, + Namespace: metrics.Namespace, Subsystem: "statedb", Name: "table_revision", Help: "The current revision of a given table.", Disabled: true, - }, []string{"table"}), + }, []string{labelTable}), TableDeleteTrackerCount: metric.NewGaugeVec(metric.GaugeOpts{ - Namespace: metrics.CiliumAgentNamespace, + Namespace: metrics.Namespace, Subsystem: "statedb", Name: "table_delete_trackers", Help: "The amount of delete trackers for a given table.", Disabled: true, - }, []string{"table"}), + }, []string{labelTable}), TableGraveyardObjectCount: metric.NewGaugeVec(metric.GaugeOpts{ - Namespace: metrics.CiliumAgentNamespace, + Namespace: metrics.Namespace, Subsystem: "statedb", Name: "table_graveyard_objects", Help: "The amount of objects in the graveyard for a given table.", Disabled: true, - }, []string{"table"}), + }, []string{labelTable}), TableGraveyardLowWatermark: metric.NewGaugeVec(metric.GaugeOpts{ - Namespace: metrics.CiliumAgentNamespace, + Namespace: metrics.Namespace, Subsystem: "statedb", Name: "table_graveyard_low_watermark", Help: "The lowest revision of a given table that has been processed by the graveyard garbage collector.", Disabled: true, - }, []string{"table"}), + }, []string{labelTable}), TableGraveyardCleaningDuration: metric.NewHistogramVec(metric.HistogramOpts{ - Namespace: metrics.CiliumAgentNamespace, + Namespace: metrics.Namespace, Subsystem: "statedb", Name: "table_graveyard_cleaning_duration_seconds", Help: "The time it took to clean the graveyard for a given table.", Disabled: true, - }, []string{"table"}), + }, []string{labelTable}), } - return m, stateDBMetricsImpl{m} + return m +} + +func NewStateDBMetricsImpl(m StateDBMetrics) statedb.Metrics { + return stateDBMetricsImpl{m} } diff --git a/vendor/github.com/cilium/cilium/pkg/hubble/helpers.go b/vendor/github.com/cilium/cilium/pkg/hubble/helpers.go new file mode 100644 index 0000000000..69f7fa4279 --- /dev/null +++ b/vendor/github.com/cilium/cilium/pkg/hubble/helpers.go @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Cilium + +package hubble + +import ( + "encoding/json" + "fmt" + "strings" + + flowpb "github.com/cilium/cilium/api/v1/flow" +) + +func ParseFlowFilters(args ...string) ([]*flowpb.FlowFilter, error) { + filters := make([]*flowpb.FlowFilter, 0, len(args)) + for _, enc := range args { + dec := json.NewDecoder(strings.NewReader(enc)) + var filter flowpb.FlowFilter + if err := dec.Decode(&filter); err != nil { + return nil, fmt.Errorf("failed to decode flow filter '%v': %w", enc, err) + } + filters = append(filters, &filter) + } + return filters, nil +} diff --git a/vendor/github.com/cilium/cilium/pkg/identity/identity.go b/vendor/github.com/cilium/cilium/pkg/identity/identity.go index 7e96d93cc1..d55de2cac2 100644 --- a/vendor/github.com/cilium/cilium/pkg/identity/identity.go +++ b/vendor/github.com/cilium/cilium/pkg/identity/identity.go @@ -8,6 +8,7 @@ import ( "fmt" "net" "strconv" + "sync" "github.com/cilium/cilium/pkg/labels" "github.com/cilium/cilium/pkg/option" @@ -322,3 +323,8 @@ func IdentityAllocationIsLocal(lbls labels.Labels) bool { // key, the well-known identity for it can be allocated locally. return LookupReservedIdentityByLabels(lbls) != nil } + +// UpdateIdentities is an interface to be called when identities change +type UpdateIdentities interface { + UpdateIdentities(added, deleted IdentityMap, wg *sync.WaitGroup) +} diff --git a/vendor/github.com/cilium/cilium/pkg/identity/identitymanager/cell.go b/vendor/github.com/cilium/cilium/pkg/identity/identitymanager/cell.go index 5b1fe39d78..bb8e812797 100644 --- a/vendor/github.com/cilium/cilium/pkg/identity/identitymanager/cell.go +++ b/vendor/github.com/cilium/cilium/pkg/identity/identitymanager/cell.go @@ -8,5 +8,5 @@ import "github.com/cilium/hive/cell" var Cell = cell.Module( "identity-manager", "Identity manager tracks identities assigned to locally managed endpoints ", - cell.Provide(NewIdentityManager), + cell.Provide(NewIDManager), ) diff --git a/vendor/github.com/cilium/cilium/pkg/identity/identitymanager/manager.go b/vendor/github.com/cilium/cilium/pkg/identity/identitymanager/manager.go index 2f36277e64..03813c60c3 100644 --- a/vendor/github.com/cilium/cilium/pkg/identity/identitymanager/manager.go +++ b/vendor/github.com/cilium/cilium/pkg/identity/identitymanager/manager.go @@ -13,6 +13,15 @@ import ( "github.com/cilium/cilium/pkg/logging/logfields" ) +type IDManager interface { + Add(identity *identity.Identity) + GetIdentityModels() []*models.IdentityEndpoints + Remove(identity *identity.Identity) + RemoveAll() + RemoveOldAddNew(old *identity.Identity, new *identity.Identity) + Subscribe(o Observer) +} + // IdentityManager caches information about a set of identities, currently a // reference count of how many users there are for each identity. type IdentityManager struct { @@ -21,13 +30,17 @@ type IdentityManager struct { observers map[Observer]struct{} } +// NewIDManager returns an initialized IdentityManager. +func NewIDManager() IDManager { + return newIdentityManager() +} + type identityMetadata struct { identity *identity.Identity refCount uint } -// NewIdentityManager returns an initialized IdentityManager. -func NewIdentityManager() *IdentityManager { +func newIdentityManager() *IdentityManager { return &IdentityManager{ identities: make(map[identity.NumericIdentity]*identityMetadata), observers: make(map[Observer]struct{}), diff --git a/vendor/github.com/cilium/cilium/pkg/inctimer/inctimer.go b/vendor/github.com/cilium/cilium/pkg/inctimer/inctimer.go deleted file mode 100644 index 072c02a56e..0000000000 --- a/vendor/github.com/cilium/cilium/pkg/inctimer/inctimer.go +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of Cilium - -package inctimer - -import "time" - -// IncTimer should be the preferred mechanism over -// calling `time.After` when wanting an `After`-like -// function in a loop. This prevents memory build up -// as the `time.After` method creates a new timer -// instance every time it is called, and it is not -// garbage collected until after it fires. Conversely, -// IncTimer only uses one timer and correctly stops -// the timer, clears its channel, and resets it -// everytime that `After` is called. -type IncTimer interface { - After(time.Duration) <-chan time.Time -} - -type incTimer struct { - t *time.Timer -} - -// New creates a new IncTimer and a done function. -// IncTimer only uses one timer and correctly stops -// the timer, clears the channel, and resets it every -// time the `After` function is called. -// WARNING: Concurrent use is not expected. The use -// of this timer should be for only one goroutine. -func New() (IncTimer, func() bool) { - it := &incTimer{} - return it, it.stop -} - -// stop returns true if a scheduled timer has been stopped before execution. -func (it *incTimer) stop() bool { - if it.t == nil { - return false - } - return it.t.Stop() -} - -// After returns a channel that will fire after -// the specified duration. -func (it *incTimer) After(d time.Duration) <-chan time.Time { - // Stop the previous timer (if any) to garbage collect it. - // The old timer channel will be garbage collected even if not drained. - it.stop() - - // We have to create a new timer for each invocation, because it is not - // possible to safely use https://golang.org/pkg/time/#Timer.Reset if we - // do not know if the timer channel has already been drained or not (which - // is the case here, as the client might have drained the channel already). - // Even after stopping a timer, it's not safe to attempt to drain its - // timer channel with a default case (for the case where the client has - // drained the channel already), as there is a small window where a timer - // is considered expired, but the channel has not received a value yet [1]. - // This would cause us to erroneously take the default case (assuming the - // channel has been drained by the client), when in fact the channel just - // has not received a value yet. Because the two cases (client has drained - // vs. value not received yet) are indistinguishable for us, we cannot use - // Timer.Reset and need to create a new timer. - // - // [1] The reason why this small window occurs, is because the Go runtime - // will remove a timer from the heap and and mark it as deleted _before_ - // it actually executes the timer function f: - // https://github.com/golang/go/blob/go1.16/src/runtime/time.go#L876 - // This causes t.Stop to report the timer as already expired while it is - // in fact currently running: - // https://github.com/golang/go/blob/go1.16/src/runtime/time.go#L352 - it.t = time.NewTimer(d) - return it.t.C -} - -// After wraps the time.After function to get around the /timeafter linter -// warning for cases where it is inconvenient to use the instantiated version. -func After(d time.Duration) <-chan time.Time { - return time.After(d) -} diff --git a/vendor/github.com/cilium/cilium/pkg/ip/ip.go b/vendor/github.com/cilium/cilium/pkg/ip/ip.go index 5563ae7f3e..7f457f340c 100644 --- a/vendor/github.com/cilium/cilium/pkg/ip/ip.go +++ b/vendor/github.com/cilium/cilium/pkg/ip/ip.go @@ -9,11 +9,10 @@ import ( "math/big" "net" "net/netip" + "slices" "sort" "go4.org/netipx" - - "github.com/cilium/cilium/pkg/slices" ) const ( @@ -753,15 +752,8 @@ func PartitionCIDR(targetCIDR net.IPNet, excludeCIDR net.IPNet) ([]*net.IPNet, [ // netip.Addr.Compare (i.e. IPv4 addresses show up before IPv6). // The slice is manipulated in-place destructively; it does not create a new slice. func KeepUniqueAddrs(addrs []netip.Addr) []netip.Addr { - return slices.SortedUniqueFunc( - addrs, - func(a, b netip.Addr) int { - return a.Compare(b) - }, - func(a, b netip.Addr) bool { - return a == b - }, - ) + SortAddrList(addrs) + return slices.Compact(addrs) } var privateIPBlocks []*net.IPNet @@ -847,15 +839,11 @@ func ListContainsIP(ipList []net.IP, ip net.IP) bool { // SortIPList sorts the provided net.IP slice in place. func SortIPList(ipList []net.IP) { - sort.Slice(ipList, func(i, j int) bool { - return bytes.Compare(ipList[i], ipList[j]) < 0 - }) + slices.SortFunc(ipList, func(a, b net.IP) int { return bytes.Compare(a, b) }) } func SortAddrList(ipList []netip.Addr) { - sort.Slice(ipList, func(i, j int) bool { - return ipList[i].Compare(ipList[j]) < 0 - }) + slices.SortFunc(ipList, netip.Addr.Compare) } // getSortedIPList returns a new net.IP slice in which the IPs are sorted. diff --git a/vendor/github.com/cilium/cilium/pkg/ipam/types/types.go b/vendor/github.com/cilium/cilium/pkg/ipam/types/types.go index 81c6e5ebcd..5f938642ee 100644 --- a/vendor/github.com/cilium/cilium/pkg/ipam/types/types.go +++ b/vendor/github.com/cilium/cilium/pkg/ipam/types/types.go @@ -178,6 +178,13 @@ type IPAMSpec struct { // // +kubebuilder:validation:Minimum=0 MaxAboveWatermark int `json:"max-above-watermark,omitempty"` + + // StaticIPTags are used to determine the pool of IPs from which to + // attribute a static IP to the node. For example in AWS this is used to + // filter Elastic IP Addresses. + // + // +optional + StaticIPTags map[string]string `json:"static-ip-tags,omitempty"` } // IPReleaseStatus defines the valid states in IP release handshake @@ -230,6 +237,11 @@ type IPAMStatus struct { // // +optional ReleaseIPv6s map[string]IPReleaseStatus `json:"release-ipv6s,omitempty"` + + // AssignedStaticIP is the static IP assigned to the node (ex: public Elastic IP address in AWS) + // + // +optional + AssignedStaticIP string `json:"assigned-static-ip,omitempty"` } // IPAMPoolRequest is a request from the agent to the operator, indicating how diff --git a/vendor/github.com/cilium/cilium/pkg/ipam/types/zz_generated.deepcopy.go b/vendor/github.com/cilium/cilium/pkg/ipam/types/zz_generated.deepcopy.go index fb9303d76c..b0af2cd053 100644 --- a/vendor/github.com/cilium/cilium/pkg/ipam/types/zz_generated.deepcopy.go +++ b/vendor/github.com/cilium/cilium/pkg/ipam/types/zz_generated.deepcopy.go @@ -151,6 +151,13 @@ func (in *IPAMSpec) DeepCopyInto(out *IPAMSpec) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.StaticIPTags != nil { + in, out := &in.StaticIPTags, &out.StaticIPTags + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } return } diff --git a/vendor/github.com/cilium/cilium/pkg/ipam/types/zz_generated.deepequal.go b/vendor/github.com/cilium/cilium/pkg/ipam/types/zz_generated.deepequal.go index cc2d82b011..d0065ee549 100644 --- a/vendor/github.com/cilium/cilium/pkg/ipam/types/zz_generated.deepequal.go +++ b/vendor/github.com/cilium/cilium/pkg/ipam/types/zz_generated.deepequal.go @@ -211,6 +211,26 @@ func (in *IPAMSpec) DeepEqual(other *IPAMSpec) bool { if in.MaxAboveWatermark != other.MaxAboveWatermark { return false } + if ((in.StaticIPTags != nil) && (other.StaticIPTags != nil)) || ((in.StaticIPTags == nil) != (other.StaticIPTags == nil)) { + in, other := &in.StaticIPTags, &other.StaticIPTags + if other == nil { + return false + } + + if len(*in) != len(*other) { + return false + } else { + for key, inValue := range *in { + if otherValue, present := (*other)[key]; !present { + return false + } else { + if inValue != otherValue { + return false + } + } + } + } + } return true } @@ -289,6 +309,10 @@ func (in *IPAMStatus) DeepEqual(other *IPAMStatus) bool { } } + if in.AssignedStaticIP != other.AssignedStaticIP { + return false + } + return true } diff --git a/vendor/github.com/cilium/cilium/pkg/ipcache/types/types.go b/vendor/github.com/cilium/cilium/pkg/ipcache/types/types.go index 34f3ce6b8d..ee19ffb904 100644 --- a/vendor/github.com/cilium/cilium/pkg/ipcache/types/types.go +++ b/vendor/github.com/cilium/cilium/pkg/ipcache/types/types.go @@ -63,6 +63,14 @@ func NewResourceID(kind ResourceKind, namespace, name string) ResourceID { return ResourceID(str.String()) } +func (r ResourceID) Namespace() string { + parts := strings.SplitN(string(r), "/", 3) + if len(parts) < 2 { + return "" + } + return parts[1] +} + // TunnelPeer is the IP address of the host associated with this prefix. This is // typically used to establish a tunnel, e.g. in tunnel mode or for encryption. // This type implements ipcache.IPMetadata diff --git a/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/register.go b/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/register.go index f76456ac38..b65d3a29ef 100644 --- a/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/register.go +++ b/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/register.go @@ -15,5 +15,5 @@ const ( // // Maintainers: Run ./Documentation/check-crd-compat-table.sh for each release // Developers: Bump patch for each change in the CRD schema. - CustomResourceDefinitionSchemaVersion = "1.30.2" + CustomResourceDefinitionSchemaVersion = "1.30.4" ) diff --git a/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/utils/utils.go b/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/utils/utils.go index a0ea8b57a4..b88ddedad5 100644 --- a/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/utils/utils.go +++ b/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/utils/utils.go @@ -203,7 +203,9 @@ func parseToCiliumEgressCommonRule(namespace string, es api.EndpointSelector, eg if egr.ToEndpoints != nil { retRule.ToEndpoints = make([]api.EndpointSelector, len(egr.ToEndpoints)) for j, ep := range egr.ToEndpoints { - retRule.ToEndpoints[j] = getEndpointSelector(namespace, ep.LabelSelector, true, matchesInit) + endpointSelector := getEndpointSelector(namespace, ep.LabelSelector, true, matchesInit) + endpointSelector.Generated = ep.Generated + retRule.ToEndpoints[j] = endpointSelector } } @@ -326,7 +328,9 @@ func ParseToCiliumRule(namespace, name string, uid types.UID, r *api.Rule) *api. retRule.EndpointSelector = api.NewESFromK8sLabelSelector("", r.EndpointSelector.LabelSelector) // The PodSelector should only reflect to the same namespace // the policy is being stored, thus we add the namespace to - // the MatchLabels map. + // the MatchLabels map. Additionally, Policy repository relies + // on this fact to properly choose correct network policies for + // a given Security Identity. // // Policies applying to all namespaces are a special case. // Such policies can match on any traffic from Pods or Nodes, diff --git a/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2/types.go b/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2/types.go index 7e0641258b..d07a0a8a61 100644 --- a/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2/types.go +++ b/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2/types.go @@ -24,7 +24,6 @@ import ( // +kubebuilder:printcolumn:JSONPath=".status.identity.id",description="Security Identity",name="Security Identity",type=integer // +kubebuilder:printcolumn:JSONPath=".status.policy.ingress.state",description="Ingress enforcement in the endpoint",name="Ingress Enforcement",type=string,priority=1 // +kubebuilder:printcolumn:JSONPath=".status.policy.egress.state",description="Egress enforcement in the endpoint",name="Egress Enforcement",type=string,priority=1 -// +kubebuilder:printcolumn:JSONPath=".status.visibility-policy-status",description="Status of visibility policy in the endpoint",name="Visibility Policy",type=string,priority=1 // +kubebuilder:printcolumn:JSONPath=".status.state",description="Endpoint current state",name="Endpoint State",type=string // +kubebuilder:printcolumn:JSONPath=".status.networking.addressing[0].ipv4",description="Endpoint IPv4 address",name="IPv4",type=string // +kubebuilder:printcolumn:JSONPath=".status.networking.addressing[0].ipv6",description="Endpoint IPv6 address",name="IPv6",type=string @@ -77,8 +76,6 @@ type EndpointStatus struct { Policy *EndpointPolicy `json:"policy,omitempty"` - VisibilityPolicyStatus *string `json:"visibility-policy-status,omitempty"` - // State is the state of the endpoint. // // +kubebuilder:validation:Enum=creating;waiting-for-identity;not-ready;waiting-to-regenerate;regenerating;restoring;ready;disconnecting;disconnected;invalid diff --git a/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2/zz_generated.deepcopy.go b/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2/zz_generated.deepcopy.go index 029eeacecf..dcfda03400 100644 --- a/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2/zz_generated.deepcopy.go +++ b/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2/zz_generated.deepcopy.go @@ -1203,11 +1203,6 @@ func (in *EndpointStatus) DeepCopyInto(out *EndpointStatus) { *out = new(EndpointPolicy) (*in).DeepCopyInto(*out) } - if in.VisibilityPolicyStatus != nil { - in, out := &in.VisibilityPolicyStatus, &out.VisibilityPolicyStatus - *out = new(string) - **out = **in - } if in.NamedPorts != nil { in, out := &in.NamedPorts, &out.NamedPorts *out = make(models.NamedPorts, len(*in)) diff --git a/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2/zz_generated.deepequal.go b/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2/zz_generated.deepequal.go index c0727c305e..7a8c9f35ea 100644 --- a/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2/zz_generated.deepequal.go +++ b/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2/zz_generated.deepequal.go @@ -921,14 +921,6 @@ func (in *EndpointStatus) DeepEqual(other *EndpointStatus) bool { } } - if (in.VisibilityPolicyStatus == nil) != (other.VisibilityPolicyStatus == nil) { - return false - } else if in.VisibilityPolicyStatus != nil { - if *in.VisibilityPolicyStatus != *other.VisibilityPolicyStatus { - return false - } - } - if in.State != other.State { return false } diff --git a/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1/bgp_cluster_types.go b/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1/bgp_cluster_types.go index dad7bb87f2..a0a4dae4fb 100644 --- a/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1/bgp_cluster_types.go +++ b/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1/bgp_cluster_types.go @@ -14,6 +14,7 @@ import ( // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +kubebuilder:resource:categories={cilium,ciliumbgp},singular="ciliumbgpclusterconfig",path="ciliumbgpclusterconfigs",scope="Cluster",shortName={cbgpcluster} // +kubebuilder:printcolumn:JSONPath=".metadata.creationTimestamp",name="Age",type=date +// +kubebuilder:subresource:status // +kubebuilder:storageversion // CiliumBGPClusterConfig is the Schema for the CiliumBGPClusterConfig API @@ -25,6 +26,11 @@ type CiliumBGPClusterConfig struct { // Spec defines the desired cluster configuration of the BGP control plane. Spec CiliumBGPClusterConfigSpec `json:"spec"` + + // Status is a running status of the cluster configuration + // + // +kubebuilder:validation:Optional + Status CiliumBGPClusterConfigStatus `json:"status"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -136,3 +142,23 @@ type PeerConfigReference struct { // +kubebuilder:validation:Required Name string `json:"name"` } + +type CiliumBGPClusterConfigStatus struct { + // The current conditions of the CiliumBGPClusterConfig + // + // +optional + // +listType=map + // +listMapKey=type + // +deepequal-gen=false + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +// Conditions for CiliumBGPClusterConfig +const ( + // Node selector selects nothing + BGPClusterConfigConditionNoMatchingNode = "cilium.io/NoMatchingNode" + // Referenced peer configs are missing + BGPClusterConfigConditionMissingPeerConfigs = "cilium.io/MissingPeerConfigs" + // ClusterConfig with conflicting nodeSelector present + BGPClusterConfigConditionConflictingClusterConfigs = "cilium.io/ConflictingClusterConfig" +) diff --git a/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1/bgp_peer_types.go b/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1/bgp_peer_types.go index dc4cdb0336..f0e323bb2e 100644 --- a/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1/bgp_peer_types.go +++ b/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1/bgp_peer_types.go @@ -28,6 +28,7 @@ type CiliumBGPPeerConfigList struct { // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +kubebuilder:resource:categories={cilium,ciliumbgp},singular="ciliumbgppeerconfig",path="ciliumbgppeerconfigs",scope="Cluster",shortName={cbgppeer} // +kubebuilder:printcolumn:JSONPath=".metadata.creationTimestamp",name="Age",type=date +// +kubebuilder:subresource:status // +kubebuilder:storageversion type CiliumBGPPeerConfig struct { @@ -38,6 +39,11 @@ type CiliumBGPPeerConfig struct { // Spec is the specification of the desired behavior of the CiliumBGPPeerConfig. Spec CiliumBGPPeerConfigSpec `json:"spec"` + + // Status is the running status of the CiliumBGPPeerConfig + // + // +kubebuilder:validation:Optional + Status CiliumBGPPeerConfigStatus `json:"status"` } type CiliumBGPPeerConfigSpec struct { @@ -92,6 +98,22 @@ type CiliumBGPPeerConfigSpec struct { Families []CiliumBGPFamilyWithAdverts `json:"families,omitempty"` } +type CiliumBGPPeerConfigStatus struct { + // The current conditions of the CiliumBGPPeerConfig + // + // +optional + // +listType=map + // +listMapKey=type + // +deepequal-gen=false + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +// Conditions for CiliumBGPPeerConfig +const ( + // Referenced auth secret is missing + BGPPeerConfigConditionMissingAuthSecret = "cilium.io/MissingAuthSecret" +) + // CiliumBGPFamily represents a AFI/SAFI address family pair. type CiliumBGPFamily struct { // Afi is the Address Family Identifier (AFI) of the family. diff --git a/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1/zz_generated.deepcopy.go b/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1/zz_generated.deepcopy.go index 0b0bf728de..ad712afe26 100644 --- a/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1/zz_generated.deepcopy.go +++ b/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1/zz_generated.deepcopy.go @@ -241,6 +241,7 @@ func (in *CiliumBGPClusterConfig) DeepCopyInto(out *CiliumBGPClusterConfig) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) return } @@ -323,6 +324,29 @@ func (in *CiliumBGPClusterConfigSpec) DeepCopy() *CiliumBGPClusterConfigSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CiliumBGPClusterConfigStatus) DeepCopyInto(out *CiliumBGPClusterConfigStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CiliumBGPClusterConfigStatus. +func (in *CiliumBGPClusterConfigStatus) DeepCopy() *CiliumBGPClusterConfigStatus { + if in == nil { + return nil + } + out := new(CiliumBGPClusterConfigStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CiliumBGPFamily) DeepCopyInto(out *CiliumBGPFamily) { *out = *in @@ -935,6 +959,7 @@ func (in *CiliumBGPPeerConfig) DeepCopyInto(out *CiliumBGPPeerConfig) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) return } @@ -1037,6 +1062,29 @@ func (in *CiliumBGPPeerConfigSpec) DeepCopy() *CiliumBGPPeerConfigSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CiliumBGPPeerConfigStatus) DeepCopyInto(out *CiliumBGPPeerConfigStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CiliumBGPPeerConfigStatus. +func (in *CiliumBGPPeerConfigStatus) DeepCopy() *CiliumBGPPeerConfigStatus { + if in == nil { + return nil + } + out := new(CiliumBGPPeerConfigStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CiliumBGPPeeringPolicy) DeepCopyInto(out *CiliumBGPPeeringPolicy) { *out = *in diff --git a/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1/zz_generated.deepequal.go b/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1/zz_generated.deepequal.go index 2bb8aced3e..cca99d9aea 100644 --- a/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1/zz_generated.deepequal.go +++ b/vendor/github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1/zz_generated.deepequal.go @@ -243,6 +243,10 @@ func (in *CiliumBGPClusterConfig) DeepEqual(other *CiliumBGPClusterConfig) bool return false } + if !in.Status.DeepEqual(&other.Status) { + return false + } + return true } @@ -281,6 +285,16 @@ func (in *CiliumBGPClusterConfigSpec) DeepEqual(other *CiliumBGPClusterConfigSpe return true } +// DeepEqual is an autogenerated deepequal function, deeply comparing the +// receiver with other. in must be non-nil. +func (in *CiliumBGPClusterConfigStatus) DeepEqual(other *CiliumBGPClusterConfigStatus) bool { + if other == nil { + return false + } + + return true +} + // DeepEqual is an autogenerated deepequal function, deeply comparing the // receiver with other. in must be non-nil. func (in *CiliumBGPFamily) DeepEqual(other *CiliumBGPFamily) bool { @@ -960,6 +974,10 @@ func (in *CiliumBGPPeerConfig) DeepEqual(other *CiliumBGPPeerConfig) bool { return false } + if !in.Status.DeepEqual(&other.Status) { + return false + } + return true } @@ -1030,6 +1048,16 @@ func (in *CiliumBGPPeerConfigSpec) DeepEqual(other *CiliumBGPPeerConfigSpec) boo return true } +// DeepEqual is an autogenerated deepequal function, deeply comparing the +// receiver with other. in must be non-nil. +func (in *CiliumBGPPeerConfigStatus) DeepEqual(other *CiliumBGPPeerConfigStatus) bool { + if other == nil { + return false + } + + return true +} + // DeepEqual is an autogenerated deepequal function, deeply comparing the // receiver with other. in must be non-nil. func (in *CiliumBGPPeeringPolicy) DeepEqual(other *CiliumBGPPeeringPolicy) bool { diff --git a/vendor/github.com/cilium/cilium/pkg/k8s/client/cell.go b/vendor/github.com/cilium/cilium/pkg/k8s/client/cell.go index f0b6bc4360..629a182c1d 100644 --- a/vendor/github.com/cilium/cilium/pkg/k8s/client/cell.go +++ b/vendor/github.com/cilium/cilium/pkg/k8s/client/cell.go @@ -14,19 +14,25 @@ import ( "strings" "time" + "github.com/cilium/hive" "github.com/cilium/hive/cell" + "github.com/cilium/hive/script" "github.com/sirupsen/logrus" apiext_clientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" apiext_fake "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake" k8sErrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilnet "k8s.io/apimachinery/pkg/util/net" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" + versionapi "k8s.io/apimachinery/pkg/version" "k8s.io/client-go/discovery" + fakediscovery "k8s.io/client-go/discovery/fake" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/rest" + k8sTesting "k8s.io/client-go/testing" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/connrotation" mcsapi_clientset "sigs.k8s.io/mcs-api/pkg/client/clientset/versioned" @@ -42,6 +48,7 @@ import ( slim_metav1beta1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1beta1" slim_clientset "github.com/cilium/cilium/pkg/k8s/slim/k8s/client/clientset/versioned" slim_fake "github.com/cilium/cilium/pkg/k8s/slim/k8s/client/clientset/versioned/fake" + "github.com/cilium/cilium/pkg/k8s/testutils" k8sversion "github.com/cilium/cilium/pkg/k8s/version" "github.com/cilium/cilium/pkg/logging/logfields" "github.com/cilium/cilium/pkg/version" @@ -456,7 +463,17 @@ func isConnReady(c kubernetes.Interface) error { return err } -var FakeClientCell = cell.Provide(NewFakeClientset) +var FakeClientCell = cell.Module( + "k8s-fake-client", + "Fake Kubernetes client", + + cell.Provide( + NewFakeClientset, + func(fc *FakeClientset) hive.ScriptCmdOut { + return hive.NewScriptCmd("k8s", FakeClientCommand(fc)) + }, + ), +) type ( MCSAPIFakeClientset = mcsapi_fake.Clientset @@ -477,6 +494,8 @@ type FakeClientset struct { SlimFakeClientset *SlimFakeClientset + trackers map[string]k8sTesting.ObjectTracker + enabled bool } @@ -509,6 +528,19 @@ func (c *FakeClientset) RestConfig() *rest.Config { } func NewFakeClientset() (*FakeClientset, Clientset) { + version := testutils.DefaultVersion + return NewFakeClientsetWithVersion(version) +} + +func NewFakeClientsetWithVersion(version string) (*FakeClientset, Clientset) { + if version == "" { + version = testutils.DefaultVersion + } + resources, found := testutils.APIResources[version] + if !found { + panic("version " + version + " not found from testutils.APIResources") + } + client := FakeClientset{ SlimFakeClientset: slim_fake.NewSimpleClientset(), CiliumFakeClientset: cilium_fake.NewSimpleClientset(), @@ -517,10 +549,30 @@ func NewFakeClientset() (*FakeClientset, Clientset) { KubernetesFakeClientset: fake.NewSimpleClientset(), enabled: true, } + client.KubernetesFakeClientset.Resources = resources + client.SlimFakeClientset.Resources = resources + client.CiliumFakeClientset.Resources = resources + client.APIExtFakeClientset.Resources = resources + client.trackers = map[string]k8sTesting.ObjectTracker{ + "slim": client.SlimFakeClientset.Tracker(), + "cilium": client.CiliumFakeClientset.Tracker(), + "mcs": client.MCSAPIFakeClientset.Tracker(), + "kubernetes": client.KubernetesFakeClientset.Tracker(), + "apiexit": client.APIExtFakeClientset.Tracker(), + } + + fd := client.KubernetesFakeClientset.Discovery().(*fakediscovery.FakeDiscovery) + fd.FakedServerVersion = toVersionInfo(version) + client.clientsetGetters = clientsetGetters{&client} return &client, &client } +func toVersionInfo(rawVersion string) *versionapi.Info { + parts := strings.Split(rawVersion, ".") + return &versionapi.Info{Major: parts[0], Minor: parts[1]} +} + type ClientBuilderFunc func(name string) (Clientset, error) // NewClientBuilder returns a function that creates a new Clientset with the given @@ -545,6 +597,79 @@ func FakeClientBuilder() ClientBuilderFunc { } } +func FakeClientCommand(fc *FakeClientset) script.Cmd { + return script.Command( + script.CmdUsage{ + Summary: "interact with fake k8s client", + Args: " args...", + }, + func(s *script.State, args ...string) (script.WaitFunc, error) { + if len(args) < 1 { + return nil, fmt.Errorf("usage: k8s files...\n is one of add, update or delete.") + } + + action := args[0] + if len(args) < 2 { + return nil, fmt.Errorf("usage: k8s %s files...", action) + } + + for _, file := range args[1:] { + b, err := os.ReadFile(s.Path(file)) + if err != nil { + // Try relative to current directory, e.g. to allow reading "testdata/foo.yaml" + b, err = os.ReadFile(file) + } + if err != nil { + return nil, fmt.Errorf("failed to read %s: %w", file, err) + } + obj, gvk, err := testutils.DecodeObjectGVK(b) + if err != nil { + return nil, fmt.Errorf("decode: %w", err) + } + gvr, _ := meta.UnsafeGuessKindToResource(*gvk) + objMeta, err := meta.Accessor(obj) + if err != nil { + return nil, fmt.Errorf("accessor: %w", err) + } + name := objMeta.GetName() + ns := objMeta.GetNamespace() + + // Try to add the object to all the trackers. If one of them + // accepts we're good. We'll add to all since multiple trackers + // may accept (e.g. slim and kubernetes). + + // err will get set to nil if any of the tracker methods succeed. + // start with a non-nil default error. + err = fmt.Errorf("none of the trackers of FakeClientset accepted %T", obj) + for trackerName, tracker := range fc.trackers { + var trackerErr error + switch action { + case "add": + trackerErr = tracker.Add(obj) + case "update": + trackerErr = tracker.Update(gvr, obj, ns) + case "delete": + trackerErr = tracker.Delete(gvr, ns, name) + default: + return nil, fmt.Errorf("unknown k8s action %q, expected 'add', 'update' or 'delete'", action) + } + if err != nil { + if trackerErr == nil { + // One of the trackers accepted the object, it's a success! + err = nil + } else { + err = errors.Join(err, fmt.Errorf("%s: %w", trackerName, trackerErr)) + } + } + } + if err != nil { + return nil, err + } + } + return nil, nil + }) +} + func init() { // Register the metav1.Table and metav1.PartialObjectMetadata for the // apiextclientset. diff --git a/vendor/github.com/cilium/cilium/pkg/k8s/client/clientset/versioned/typed/cilium.io/v2alpha1/ciliumbgpclusterconfig.go b/vendor/github.com/cilium/cilium/pkg/k8s/client/clientset/versioned/typed/cilium.io/v2alpha1/ciliumbgpclusterconfig.go index 95fe53344f..5a4adb3c92 100644 --- a/vendor/github.com/cilium/cilium/pkg/k8s/client/clientset/versioned/typed/cilium.io/v2alpha1/ciliumbgpclusterconfig.go +++ b/vendor/github.com/cilium/cilium/pkg/k8s/client/clientset/versioned/typed/cilium.io/v2alpha1/ciliumbgpclusterconfig.go @@ -26,6 +26,8 @@ type CiliumBGPClusterConfigsGetter interface { type CiliumBGPClusterConfigInterface interface { Create(ctx context.Context, ciliumBGPClusterConfig *v2alpha1.CiliumBGPClusterConfig, opts v1.CreateOptions) (*v2alpha1.CiliumBGPClusterConfig, error) Update(ctx context.Context, ciliumBGPClusterConfig *v2alpha1.CiliumBGPClusterConfig, opts v1.UpdateOptions) (*v2alpha1.CiliumBGPClusterConfig, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, ciliumBGPClusterConfig *v2alpha1.CiliumBGPClusterConfig, opts v1.UpdateOptions) (*v2alpha1.CiliumBGPClusterConfig, error) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error Get(ctx context.Context, name string, opts v1.GetOptions) (*v2alpha1.CiliumBGPClusterConfig, error) diff --git a/vendor/github.com/cilium/cilium/pkg/k8s/client/clientset/versioned/typed/cilium.io/v2alpha1/ciliumbgppeerconfig.go b/vendor/github.com/cilium/cilium/pkg/k8s/client/clientset/versioned/typed/cilium.io/v2alpha1/ciliumbgppeerconfig.go index 8b5c9f3ce9..dace3626c7 100644 --- a/vendor/github.com/cilium/cilium/pkg/k8s/client/clientset/versioned/typed/cilium.io/v2alpha1/ciliumbgppeerconfig.go +++ b/vendor/github.com/cilium/cilium/pkg/k8s/client/clientset/versioned/typed/cilium.io/v2alpha1/ciliumbgppeerconfig.go @@ -26,6 +26,8 @@ type CiliumBGPPeerConfigsGetter interface { type CiliumBGPPeerConfigInterface interface { Create(ctx context.Context, ciliumBGPPeerConfig *v2alpha1.CiliumBGPPeerConfig, opts v1.CreateOptions) (*v2alpha1.CiliumBGPPeerConfig, error) Update(ctx context.Context, ciliumBGPPeerConfig *v2alpha1.CiliumBGPPeerConfig, opts v1.UpdateOptions) (*v2alpha1.CiliumBGPPeerConfig, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, ciliumBGPPeerConfig *v2alpha1.CiliumBGPPeerConfig, opts v1.UpdateOptions) (*v2alpha1.CiliumBGPPeerConfig, error) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error Get(ctx context.Context, name string, opts v1.GetOptions) (*v2alpha1.CiliumBGPPeerConfig, error) diff --git a/vendor/github.com/cilium/cilium/pkg/k8s/client/clientset/versioned/typed/cilium.io/v2alpha1/fake/fake_ciliumbgpclusterconfig.go b/vendor/github.com/cilium/cilium/pkg/k8s/client/clientset/versioned/typed/cilium.io/v2alpha1/fake/fake_ciliumbgpclusterconfig.go index c45f25cf1c..a9951b2477 100644 --- a/vendor/github.com/cilium/cilium/pkg/k8s/client/clientset/versioned/typed/cilium.io/v2alpha1/fake/fake_ciliumbgpclusterconfig.go +++ b/vendor/github.com/cilium/cilium/pkg/k8s/client/clientset/versioned/typed/cilium.io/v2alpha1/fake/fake_ciliumbgpclusterconfig.go @@ -86,6 +86,18 @@ func (c *FakeCiliumBGPClusterConfigs) Update(ctx context.Context, ciliumBGPClust return obj.(*v2alpha1.CiliumBGPClusterConfig), err } +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeCiliumBGPClusterConfigs) UpdateStatus(ctx context.Context, ciliumBGPClusterConfig *v2alpha1.CiliumBGPClusterConfig, opts v1.UpdateOptions) (result *v2alpha1.CiliumBGPClusterConfig, err error) { + emptyResult := &v2alpha1.CiliumBGPClusterConfig{} + obj, err := c.Fake. + Invokes(testing.NewRootUpdateSubresourceActionWithOptions(ciliumbgpclusterconfigsResource, "status", ciliumBGPClusterConfig, opts), emptyResult) + if obj == nil { + return emptyResult, err + } + return obj.(*v2alpha1.CiliumBGPClusterConfig), err +} + // Delete takes name of the ciliumBGPClusterConfig and deletes it. Returns an error if one occurs. func (c *FakeCiliumBGPClusterConfigs) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { _, err := c.Fake. diff --git a/vendor/github.com/cilium/cilium/pkg/k8s/client/clientset/versioned/typed/cilium.io/v2alpha1/fake/fake_ciliumbgppeerconfig.go b/vendor/github.com/cilium/cilium/pkg/k8s/client/clientset/versioned/typed/cilium.io/v2alpha1/fake/fake_ciliumbgppeerconfig.go index 650a69554f..be7b6693b7 100644 --- a/vendor/github.com/cilium/cilium/pkg/k8s/client/clientset/versioned/typed/cilium.io/v2alpha1/fake/fake_ciliumbgppeerconfig.go +++ b/vendor/github.com/cilium/cilium/pkg/k8s/client/clientset/versioned/typed/cilium.io/v2alpha1/fake/fake_ciliumbgppeerconfig.go @@ -86,6 +86,18 @@ func (c *FakeCiliumBGPPeerConfigs) Update(ctx context.Context, ciliumBGPPeerConf return obj.(*v2alpha1.CiliumBGPPeerConfig), err } +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeCiliumBGPPeerConfigs) UpdateStatus(ctx context.Context, ciliumBGPPeerConfig *v2alpha1.CiliumBGPPeerConfig, opts v1.UpdateOptions) (result *v2alpha1.CiliumBGPPeerConfig, err error) { + emptyResult := &v2alpha1.CiliumBGPPeerConfig{} + obj, err := c.Fake. + Invokes(testing.NewRootUpdateSubresourceActionWithOptions(ciliumbgppeerconfigsResource, "status", ciliumBGPPeerConfig, opts), emptyResult) + if obj == nil { + return emptyResult, err + } + return obj.(*v2alpha1.CiliumBGPPeerConfig), err +} + // Delete takes name of the ciliumBGPPeerConfig and deletes it. Returns an error if one occurs. func (c *FakeCiliumBGPPeerConfigs) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { _, err := c.Fake. diff --git a/vendor/github.com/cilium/cilium/pkg/k8s/client/config.go b/vendor/github.com/cilium/cilium/pkg/k8s/client/config.go index a2ecbfae8a..35b79d7742 100644 --- a/vendor/github.com/cilium/cilium/pkg/k8s/client/config.go +++ b/vendor/github.com/cilium/cilium/pkg/k8s/client/config.go @@ -40,7 +40,7 @@ type SharedConfig struct { // K8sHeartbeatTimeout configures the timeout for apiserver heartbeat K8sHeartbeatTimeout time.Duration - // K8sEnableAPIDiscovery enables Kubernetes API discovery + // EnableAPIDiscovery enables Kubernetes API discovery EnableK8sAPIDiscovery bool } diff --git a/vendor/github.com/cilium/cilium/pkg/k8s/endpoints.go b/vendor/github.com/cilium/cilium/pkg/k8s/endpoints.go index 2fecf24e35..29e612729f 100644 --- a/vendor/github.com/cilium/cilium/pkg/k8s/endpoints.go +++ b/vendor/github.com/cilium/cilium/pkg/k8s/endpoints.go @@ -450,8 +450,8 @@ type EndpointSlices struct { epSlices map[string]*Endpoints } -// newEndpointsSlices returns a new EndpointSlices -func newEndpointsSlices() *EndpointSlices { +// NewEndpointsSlices returns a new EndpointSlices +func NewEndpointsSlices() *EndpointSlices { return &EndpointSlices{ epSlices: map[string]*Endpoints{}, } diff --git a/vendor/github.com/cilium/cilium/pkg/k8s/portforward.go b/vendor/github.com/cilium/cilium/pkg/k8s/portforward.go new file mode 100644 index 0000000000..6e2e672278 --- /dev/null +++ b/vendor/github.com/cilium/cilium/pkg/k8s/portforward.go @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Cilium + +package k8s + +import ( + "context" + "fmt" + "io" + "net/http" + "sort" + "strings" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/portforward" + "k8s.io/client-go/transport/spdy" + kutil "k8s.io/kubectl/pkg/util" + "k8s.io/kubectl/pkg/util/podutils" + + "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/labels" +) + +// ForwardedPort holds the local and remote mapped ports. +type ForwardedPort struct { + Local uint16 + Remote uint16 +} + +// PortForwardParameters are the needed parameters to call PortForward. +// +// Ports value follow the kubectl syntax: :: +// - 5000 means 5000:5000 listening on 5000 port locally, forwarding to 5000 in the pod +// - 8888:5000 means listening on 8888 port locally, forwarding to 5000 in the pod +// - 0:5000 means listening on a random port locally, forwarding to 5000 in the pod +// - :5000 means listening on a random port locally, forwarding to 5000 in the pod +type PortForwardParameters struct { + Namespace string + Pod string + Ports []string + Addresses []string + OutWriters OutWriters +} + +// OutWriters holds the two io.Writer used by the port forward. +// These can be safely disabled by setting them to nil. +type OutWriters struct { + Out io.Writer + ErrOut io.Writer +} + +// PortForwarder augments the k8s client-go PortForwarder with helper methods using a clientset. +type PortForwarder struct { + clientset kubernetes.Interface + config *rest.Config +} + +// NewPortForwarder creates a new PortForwarder ready to use. +func NewPortForwarder(clientset kubernetes.Interface, config *rest.Config) *PortForwarder { + return &PortForwarder{clientset: clientset, config: config} +} + +// PortForwardResult are the ports that have been forwarded by PortForward. +type PortForwardResult struct { + ForwardedPorts []ForwardedPort +} + +// PortForward executes in a goroutine a port forward command. +// To stop the port-forwarding, use the context by cancelling it. +func (pf *PortForwarder) PortForward(ctx context.Context, p PortForwardParameters) (*PortForwardResult, error) { + req := pf.clientset.CoreV1().RESTClient().Post().Namespace(p.Namespace). + Resource("pods").Name(p.Pod).SubResource(strings.ToLower("PortForward")) + + roundTripper, upgrader, err := spdy.RoundTripperFor(pf.config) + if err != nil { + return nil, err + } + + dialer := spdy.NewDialer(upgrader, &http.Client{Transport: roundTripper}, http.MethodPost, req.URL()) + stopChan, readyChan := make(chan struct{}, 1), make(chan struct{}, 1) + if len(p.Addresses) == 0 { + p.Addresses = []string{"localhost"} + } + + pw, err := portforward.NewOnAddresses(dialer, p.Addresses, p.Ports, stopChan, readyChan, p.OutWriters.Out, p.OutWriters.ErrOut) + if err != nil { + return nil, err + } + + errChan := make(chan error, 1) + go func() { + if err := pw.ForwardPorts(); err != nil { + errChan <- err + } + }() + + go func() { + <-ctx.Done() + close(stopChan) + }() + + select { + case <-pw.Ready: + case <-ctx.Done(): + return nil, ctx.Err() + case err := <-errChan: + return nil, err + } + + ports, err := pw.GetPorts() + if err != nil { + return nil, err + } + + forwardedPorts := make([]ForwardedPort, 0, len(ports)) + for _, port := range ports { + forwardedPorts = append(forwardedPorts, ForwardedPort{port.Local, port.Remote}) + } + + return &PortForwardResult{ + ForwardedPorts: forwardedPorts, + }, nil +} + +// PortForwardServiceResult are the ports that have been forwarded by PortForwardService. +type PortForwardServiceResult struct { + ForwardedPort ForwardedPort +} + +// PortForwardService executes in a goroutine a port forward command towards one of the pod behind a +// service. If `localPort` is 0, a random port is selected. If `svcPort` is 0, uses the first port +// configured on the service. +// +// To stop the port-forwarding, use the context by cancelling it. +func (pf *PortForwarder) PortForwardService(ctx context.Context, namespace, name string, localPort, svcPort int32) (*PortForwardServiceResult, error) { + svc, err := pf.clientset.CoreV1().Services(namespace).Get(ctx, name, metav1.GetOptions{}) + if err != nil { + return nil, fmt.Errorf("failed to get service %q: %w", name, err) + } + + pod, err := pf.getFirstPodForService(ctx, svc) + if err != nil { + return nil, fmt.Errorf("failed to get service %q: %w", name, err) + } + + if svcPort == 0 { + svcPort = svc.Spec.Ports[0].Port + } + + containerPort, err := kutil.LookupContainerPortNumberByServicePort(*svc, *pod, svcPort) + if err != nil { + return nil, fmt.Errorf("failed to lookup container port with service port %d: %w", svcPort, err) + } + + p := PortForwardParameters{ + Namespace: pod.Namespace, + Pod: pod.Name, + Ports: []string{fmt.Sprintf("%d:%d", localPort, containerPort)}, + Addresses: nil, // default is localhost + OutWriters: OutWriters{Out: nil, ErrOut: nil}, + } + + res, err := pf.PortForward(ctx, p) + if err != nil { + return nil, fmt.Errorf("failed to port forward: %w", err) + } + + return &PortForwardServiceResult{ + ForwardedPort: res.ForwardedPorts[0], + }, nil +} + +// getFirstPodForService returns the first pod in the list of pods matching the service selector, +// sorted from most to less active (see `podutils.ActivePods` for more details). +func (pf *PortForwarder) getFirstPodForService(ctx context.Context, svc *corev1.Service) (*corev1.Pod, error) { + selector := labels.SelectorFromSet(svc.Spec.Selector) + podList, err := pf.clientset.CoreV1().Pods(svc.Namespace).List(ctx, metav1.ListOptions{LabelSelector: selector.String()}) + if err != nil { + return nil, fmt.Errorf("failed to get list of pods for service %q: %w", svc.Name, err) + } + if len(podList.Items) == 0 { + return nil, fmt.Errorf("no pods found for service: %s", svc.Name) + } + if len(podList.Items) == 1 { + return &podList.Items[0], nil + } + + pods := make([]*corev1.Pod, 0, len(podList.Items)) + for _, pod := range podList.Items { + pods = append(pods, &pod) + } + sortBy := func(pods []*corev1.Pod) sort.Interface { return sort.Reverse(podutils.ActivePods(pods)) } + sort.Sort(sortBy(pods)) + + return pods[0], nil +} diff --git a/vendor/github.com/cilium/cilium/pkg/k8s/service.go b/vendor/github.com/cilium/cilium/pkg/k8s/service.go index 6e84cff86b..065b48f4bb 100644 --- a/vendor/github.com/cilium/cilium/pkg/k8s/service.go +++ b/vendor/github.com/cilium/cilium/pkg/k8s/service.go @@ -229,7 +229,7 @@ func ParseService(svc *slim_corev1.Service, nodePortAddrs []netip.Addr) (Service if expType.canExpose(slim_corev1.ServiceTypeLoadBalancer) { for _, ip := range svc.Status.LoadBalancer.Ingress { - if ip.IP != "" { + if ip.IP != "" && ip.IPMode == nil || *ip.IPMode == slim_corev1.LoadBalancerIPModeVIP { loadBalancerIPs = append(loadBalancerIPs, ip.IP) } } diff --git a/vendor/github.com/cilium/cilium/pkg/k8s/service_cache.go b/vendor/github.com/cilium/cilium/pkg/k8s/service_cache.go index 600a679fab..3c4f10e6b0 100644 --- a/vendor/github.com/cilium/cilium/pkg/k8s/service_cache.go +++ b/vendor/github.com/cilium/cilium/pkg/k8s/service_cache.go @@ -301,7 +301,7 @@ func (s *ServiceCache) GetEndpointsOfService(svcID ServiceID) *Endpoints { // Services are iterated in random order. // The ServiceCache is read-locked during this function call. The passed in // Service and Endpoints references are read-only. -func (s *ServiceCache) ForEachService(yield func(svcID ServiceID, svc *Service, eps *Endpoints) bool) { +func (s *ServiceCache) ForEachService(yield func(svcID ServiceID, svc *Service, eps *EndpointSlices) bool) { s.mutex.RLock() defer s.mutex.RUnlock() @@ -310,8 +310,7 @@ func (s *ServiceCache) ForEachService(yield func(svcID ServiceID, svc *Service, if !ok { continue } - eps := ep.GetEndpoints() - if !yield(svcID, svc, eps) { + if !yield(svcID, svc, ep) { return } } @@ -454,7 +453,7 @@ func (s *ServiceCache) UpdateEndpoints(newEndpoints *Endpoints, swg *lock.Stoppa return esID.ServiceID, newEndpoints } } else { - eps = newEndpointsSlices() + eps = NewEndpointsSlices() s.endpoints[esID.ServiceID] = eps } @@ -628,19 +627,20 @@ func (s *ServiceCache) filterEndpoints(localEndpoints *Endpoints, svc *Service) // // OR Remote endpoints exist which correlate to the service. func (s *ServiceCache) correlateEndpoints(id ServiceID) (*Endpoints, bool) { - endpoints := newEndpoints() - - localEndpoints := s.endpoints[id].GetEndpoints() + endpoints := s.endpoints[id].GetEndpoints() svc, svcFound := s.services[id] - hasLocalEndpoints := localEndpoints != nil + hasLocalEndpoints := endpoints != nil if hasLocalEndpoints { - localEndpoints = s.filterEndpoints(localEndpoints, svc) + endpoints = s.filterEndpoints(endpoints, svc) - for ip, e := range localEndpoints.Backends { + for _, e := range endpoints.Backends { + // The endpoints returned by GetEndpoints are already deep copies, + // hence we can mutate them in-place without problems. e.Preferred = svcFound && svc.IncludeExternal && svc.ServiceAffinity == serviceAffinityLocal - endpoints.Backends[ip] = e.DeepCopy() } + } else { + endpoints = newEndpoints() } var hasExternalEndpoints bool diff --git a/vendor/github.com/cilium/cilium/pkg/k8s/synced/crd.go b/vendor/github.com/cilium/cilium/pkg/k8s/synced/crd.go index 86b23a3054..c4dcbdffae 100644 --- a/vendor/github.com/cilium/cilium/pkg/k8s/synced/crd.go +++ b/vendor/github.com/cilium/cilium/pkg/k8s/synced/crd.go @@ -37,11 +37,8 @@ func CRDResourceName(crd string) string { func agentCRDResourceNames() []string { result := []string{ - CRDResourceName(v2.CNPName), - CRDResourceName(v2.CCNPName), CRDResourceName(v2.CNName), CRDResourceName(v2.CIDName), - CRDResourceName(v2alpha1.CCGName), CRDResourceName(v2alpha1.CPIPName), } @@ -52,6 +49,18 @@ func agentCRDResourceNames() []string { } } + if option.Config.EnableCiliumNetworkPolicy { + result = append(result, CRDResourceName(v2.CNPName)) + } + + if option.Config.EnableCiliumClusterwideNetworkPolicy { + result = append(result, CRDResourceName(v2.CCNPName)) + } + + if option.Config.EnableCiliumNetworkPolicy || option.Config.EnableCiliumClusterwideNetworkPolicy { + result = append(result, CRDResourceName(v2alpha1.CCGName)) + } + if option.Config.EnableIPv4EgressGateway { result = append(result, CRDResourceName(v2.CEGPName)) } diff --git a/vendor/github.com/cilium/cilium/pkg/k8s/synced/resources.go b/vendor/github.com/cilium/cilium/pkg/k8s/synced/resources.go index 32280dca04..4512d3e163 100644 --- a/vendor/github.com/cilium/cilium/pkg/k8s/synced/resources.go +++ b/vendor/github.com/cilium/cilium/pkg/k8s/synced/resources.go @@ -9,7 +9,6 @@ import ( "golang.org/x/sync/errgroup" "k8s.io/client-go/tools/cache" - "github.com/cilium/cilium/pkg/inctimer" "github.com/cilium/cilium/pkg/lock" "github.com/cilium/cilium/pkg/time" ) @@ -185,7 +184,7 @@ func (r *Resources) WaitForCacheSyncWithTimeout(timeout time.Duration, resourceN // If timeout is reached, check if an event occurred that would // have pushed back the timeout and wait for that amount of time. select { - case now := <-inctimer.After(currTimeout): + case now := <-time.After(currTimeout): lastEvent, never := r.getTimeOfLastEvent(resource) if never { return fmt.Errorf("timed out after %s, never received event for resource %q", timeout, resource) diff --git a/vendor/github.com/cilium/cilium/pkg/k8s/testutils/decoder.go b/vendor/github.com/cilium/cilium/pkg/k8s/testutils/decoder.go index 61ae4c4275..dec8f753cd 100644 --- a/vendor/github.com/cilium/cilium/pkg/k8s/testutils/decoder.go +++ b/vendor/github.com/cilium/cilium/pkg/k8s/testutils/decoder.go @@ -8,6 +8,7 @@ import ( "sync" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" @@ -63,6 +64,11 @@ func DecodeObject(bytes []byte) (runtime.Object, error) { return obj, err } +func DecodeObjectGVK(bytes []byte) (runtime.Object, *schema.GroupVersionKind, error) { + obj, gvk, err := Decoder().Decode(bytes, nil, nil) + return obj, gvk, err +} + func DecodeFile(path string) (runtime.Object, error) { bs, err := os.ReadFile(path) if err != nil { diff --git a/vendor/github.com/cilium/cilium/pkg/k8s/testutils/resources.go b/vendor/github.com/cilium/cilium/pkg/k8s/testutils/resources.go new file mode 100644 index 0000000000..2944a47d7a --- /dev/null +++ b/vendor/github.com/cilium/cilium/pkg/k8s/testutils/resources.go @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Cilium + +package testutils + +import ( + corev1 "k8s.io/api/core/v1" + discov1 "k8s.io/api/discovery/v1" + discov1beta1 "k8s.io/api/discovery/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + cilium_v2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" +) + +var ( + DefaultVersion = "1.26" + + // APIResources is the list of API resources for the k8s version that we're mocking. + // This is mostly relevant for the feature detection at pkg/k8s/version/version.go. + // The lists here are currently not exhaustive and expanded on need-by-need basis. + APIResources = map[string][]*metav1.APIResourceList{ + "1.16": { + CoreV1APIResources, + CiliumV2APIResources, + }, + "1.24": { + CoreV1APIResources, + DiscoveryV1APIResources, + DiscoveryV1Beta1APIResources, + CiliumV2APIResources, + }, + "1.25": { + CoreV1APIResources, + DiscoveryV1APIResources, + CiliumV2APIResources, + }, + "1.26": { + CoreV1APIResources, + DiscoveryV1APIResources, + CiliumV2APIResources, + }, + } + + CoreV1APIResources = &metav1.APIResourceList{ + GroupVersion: corev1.SchemeGroupVersion.String(), + APIResources: []metav1.APIResource{ + {Name: "nodes", Kind: "Node"}, + {Name: "pods", Namespaced: true, Kind: "Pod"}, + {Name: "services", Namespaced: true, Kind: "Service"}, + {Name: "endpoints", Namespaced: true, Kind: "Endpoint"}, + }, + } + + CiliumV2APIResources = &metav1.APIResourceList{ + TypeMeta: metav1.TypeMeta{}, + GroupVersion: cilium_v2.SchemeGroupVersion.String(), + APIResources: []metav1.APIResource{ + {Name: cilium_v2.CNPluralName, Kind: cilium_v2.CNKindDefinition}, + {Name: cilium_v2.CEPPluralName, Namespaced: true, Kind: cilium_v2.CEPKindDefinition}, + {Name: cilium_v2.CIDPluralName, Namespaced: true, Kind: cilium_v2.CIDKindDefinition}, + {Name: cilium_v2.CEGPPluralName, Namespaced: true, Kind: cilium_v2.CEGPKindDefinition}, + {Name: cilium_v2.CNPPluralName, Namespaced: true, Kind: cilium_v2.CNPKindDefinition}, + {Name: cilium_v2.CCNPPluralName, Namespaced: true, Kind: cilium_v2.CCNPKindDefinition}, + {Name: cilium_v2.CLRPPluralName, Namespaced: true, Kind: cilium_v2.CLRPKindDefinition}, + {Name: cilium_v2.CEWPluralName, Namespaced: true, Kind: cilium_v2.CEWKindDefinition}, + {Name: cilium_v2.CCECPluralName, Namespaced: true, Kind: cilium_v2.CCECKindDefinition}, + {Name: cilium_v2.CECPluralName, Namespaced: true, Kind: cilium_v2.CECKindDefinition}, + }, + } + + DiscoveryV1APIResources = &metav1.APIResourceList{ + TypeMeta: metav1.TypeMeta{}, + GroupVersion: discov1.SchemeGroupVersion.String(), + APIResources: []metav1.APIResource{ + {Name: "endpointslices", Namespaced: true, Kind: "EndpointSlice"}, + }, + } + + DiscoveryV1Beta1APIResources = &metav1.APIResourceList{ + GroupVersion: discov1beta1.SchemeGroupVersion.String(), + APIResources: []metav1.APIResource{ + {Name: "endpointslices", Namespaced: true, Kind: "EndpointSlice"}, + }, + } +) diff --git a/vendor/github.com/cilium/cilium/pkg/kvstore/dummy.go b/vendor/github.com/cilium/cilium/pkg/kvstore/dummy.go index 14d3841ac0..83d6991e76 100644 --- a/vendor/github.com/cilium/cilium/pkg/kvstore/dummy.go +++ b/vendor/github.com/cilium/cilium/pkg/kvstore/dummy.go @@ -9,7 +9,6 @@ import ( client "go.etcd.io/etcd/client/v3" - "github.com/cilium/cilium/pkg/inctimer" "github.com/cilium/cilium/pkg/time" ) @@ -66,9 +65,6 @@ func SetupDummyWithConfigOpts(tb testing.TB, dummyBackend string, opts map[strin tb.Fatalf("Failed waiting for kvstore connection to be established: %v", err) } - timer, done := inctimer.New() - defer done() - // Multiple tests might be running in parallel by go test if they are part of // different packages. Let's implement a locking mechanism to ensure that only // one at a time can access the kvstore, to prevent that they interact with @@ -86,7 +82,7 @@ func SetupDummyWithConfigOpts(tb testing.TB, dummyBackend string, opts map[strin } select { - case <-timer.After(100 * time.Millisecond): + case <-time.After(100 * time.Millisecond): case <-ctx.Done(): tb.Fatal("Timed out waiting to acquire the kvstore lock") } diff --git a/vendor/github.com/cilium/cilium/pkg/kvstore/etcd.go b/vendor/github.com/cilium/cilium/pkg/kvstore/etcd.go index dc43ad0f39..f865ebb774 100644 --- a/vendor/github.com/cilium/cilium/pkg/kvstore/etcd.go +++ b/vendor/github.com/cilium/cilium/pkg/kvstore/etcd.go @@ -28,7 +28,6 @@ import ( "github.com/cilium/cilium/pkg/backoff" "github.com/cilium/cilium/pkg/defaults" - "github.com/cilium/cilium/pkg/inctimer" "github.com/cilium/cilium/pkg/lock" "github.com/cilium/cilium/pkg/logging/logfields" "github.com/cilium/cilium/pkg/option" @@ -1018,9 +1017,6 @@ func (e *etcdClient) statusChecker() { var consecutiveQuorumErrors uint - statusTimer, statusTimerDone := inctimer.New() - defer statusTimerDone() - e.RWMutex.Lock() // Ensure that lastHearbeat is always set to a non-zero value when starting // the status checker, to guarantee that we can correctly compute the time @@ -1106,7 +1102,7 @@ func (e *etcdClient) statusChecker() { case <-e.stopStatusChecker: close(e.statusCheckErrors) return - case <-statusTimer.After(e.extraOptions.StatusCheckInterval(allConnected)): + case <-time.After(e.extraOptions.StatusCheckInterval(allConnected)): } } } diff --git a/vendor/github.com/cilium/cilium/pkg/kvstore/lock.go b/vendor/github.com/cilium/cilium/pkg/kvstore/lock.go index c70ee81046..b28c74f083 100644 --- a/vendor/github.com/cilium/cilium/pkg/kvstore/lock.go +++ b/vendor/github.com/cilium/cilium/pkg/kvstore/lock.go @@ -13,7 +13,6 @@ import ( "github.com/cilium/cilium/pkg/debug" "github.com/cilium/cilium/pkg/defaults" - "github.com/cilium/cilium/pkg/inctimer" "github.com/cilium/cilium/pkg/lock" "github.com/cilium/cilium/pkg/time" ) @@ -79,8 +78,6 @@ func (pl *pathLocks) runGC() { } func (pl *pathLocks) lock(ctx context.Context, path string) (id uuid.UUID, err error) { - lockTimer, lockTimerDone := inctimer.New() - defer lockTimerDone() for { pl.mutex.Lock() if _, ok := pl.lockPaths[path]; !ok { @@ -95,7 +92,7 @@ func (pl *pathLocks) lock(ctx context.Context, path string) (id uuid.UUID, err e pl.mutex.Unlock() select { - case <-lockTimer.After(time.Duration(10) * time.Millisecond): + case <-time.After(10 * time.Millisecond): case <-ctx.Done(): err = fmt.Errorf("lock was cancelled: %w", ctx.Err()) return diff --git a/vendor/github.com/cilium/cilium/pkg/labels/arraylist.go b/vendor/github.com/cilium/cilium/pkg/labels/arraylist.go index 11e5669ec1..80d1b2ae3b 100644 --- a/vendor/github.com/cilium/cilium/pkg/labels/arraylist.go +++ b/vendor/github.com/cilium/cilium/pkg/labels/arraylist.go @@ -3,7 +3,10 @@ package labels -import "sort" +import ( + "fmt" + "sort" +) // LabelArrayList is an array of LabelArrays. It is primarily intended as a // simple collection @@ -35,16 +38,70 @@ func (ls LabelArrayList) GetModel() [][]string { // Equals returns true if the label arrays lists have the same label arrays in the same order. func (ls LabelArrayList) Equals(b LabelArrayList) bool { if len(ls) != len(b) { + fmt.Printf("LEN DIFFERS: obtained %v, expected %v\n", ls, b) return false } for l := range ls { if !ls[l].Equals(b[l]) { + fmt.Printf("LABEL ARRAY %d DIFFERS: obtained %v, expected %v\n", + l, ls[l], b[l]) return false } } return true } +// Diff returns the string of differences between 'ls' and 'expected' LabelArrayList with +// '+ ' or '- ' for obtaining something unexpected, or not obtaining the expected, respectively. +// For use in debugging. Assumes sorted LabelArrayLists. +func (ls LabelArrayList) Diff(expected LabelArrayList) (res string) { + res += "" + i := 0 + j := 0 + for i < len(ls) && j < len(expected) { + if ls[i].Equals(expected[j]) { + i++ + j++ + continue + } + if ls[i].Less(expected[j]) { + // obtained has an unexpected labelArray + res += " + " + ls[i].String() + "\n" + i++ + } + for j < len(expected) && expected[j].Less(ls[i]) { + // expected has a missing labelArray + res += " - " + expected[j].String() + "\n" + j++ + } + } + for i < len(ls) { + // obtained has an unexpected labelArray + res += " + " + ls[i].String() + "\n" + i++ + } + for j < len(expected) { + // expected has a missing labelArray + res += " - " + expected[j].String() + "\n" + j++ + } + + return res +} + +// GetModel returns the LabelArrayList as a [][]string. Each member LabelArray +// becomes a []string. +func (ls LabelArrayList) String() string { + res := "" + for _, v := range ls { + if res != "" { + res += ", " + } + res += v.String() + } + return res +} + // Sort sorts the LabelArrayList in-place, but also returns the sorted list // for convenience. The LabelArrays themselves must already be sorted. This is // true for all constructors of LabelArray. diff --git a/vendor/github.com/cilium/cilium/pkg/loadbalancer/loadbalancer.go b/vendor/github.com/cilium/cilium/pkg/loadbalancer/loadbalancer.go index 8f6554459f..1a07322388 100644 --- a/vendor/github.com/cilium/cilium/pkg/loadbalancer/loadbalancer.go +++ b/vendor/github.com/cilium/cilium/pkg/loadbalancer/loadbalancer.go @@ -79,13 +79,13 @@ const ( serviceFlagIntLocalScope = 1 << 12 serviceFlagTwoScopes = 1 << 13 serviceFlagQuarantined = 1 << 14 - serviceFlagFwdModeFlip = 1 << 15 + serviceFlagFwdModeDSR = 1 << 15 ) type SvcFlagParam struct { SvcType SVCType - SvcFwdModeFlip bool SvcNatPolicy SVCNatPolicy + SvcFwdModeDSR bool SvcExtLocal bool SvcIntLocal bool SessionAffinity bool @@ -147,8 +147,8 @@ func NewSvcFlag(p *SvcFlagParam) ServiceFlags { if p.Quarantined { flags |= serviceFlagQuarantined } - if p.SvcFwdModeFlip { - flags |= serviceFlagFwdModeFlip + if p.SvcFwdModeDSR { + flags |= serviceFlagFwdModeDSR } return flags @@ -253,8 +253,8 @@ func (s ServiceFlags) String() string { if s&serviceFlagQuarantined != 0 { str = append(str, "quarantined") } - if s&serviceFlagFwdModeFlip != 0 { - str = append(str, "flip") + if s&serviceFlagFwdModeDSR != 0 { + str = append(str, "dsr") } return strings.Join(str, ", ") } diff --git a/vendor/github.com/cilium/cilium/pkg/logging/slog.go b/vendor/github.com/cilium/cilium/pkg/logging/slog.go index ec95a7e6b6..ca062fe0f3 100644 --- a/vendor/github.com/cilium/cilium/pkg/logging/slog.go +++ b/vendor/github.com/cilium/cilium/pkg/logging/slog.go @@ -11,8 +11,13 @@ import ( "time" "github.com/sirupsen/logrus" + + "github.com/cilium/cilium/pkg/logging/logfields" ) +// logrErrorKey is the key used by the logr library for the error parameter. +const logrErrorKey = "err" + // SlogNopHandler discards all logs. var SlogNopHandler slog.Handler = nopHandler{} @@ -26,7 +31,7 @@ func (n nopHandler) WithGroup(string) slog.Handler { return n } var slogHandlerOpts = &slog.HandlerOptions{ AddSource: false, Level: slog.LevelInfo, - ReplaceAttr: replaceLevelAndDropTime, + ReplaceAttr: replaceAttrFnWithoutTimestamp, } // Default slog logger. Will be overwritten once initializeSlog is called. @@ -59,9 +64,9 @@ func initializeSlog(logOpts LogOptions, useStdout bool) { logFormat := logOpts.GetLogFormat() switch logFormat { case LogFormatJSON, LogFormatText: - opts.ReplaceAttr = replaceLevelAndDropTime + opts.ReplaceAttr = replaceAttrFnWithoutTimestamp case LogFormatJSONTimestamp, LogFormatTextTimestamp: - opts.ReplaceAttr = replaceLevel + opts.ReplaceAttr = replaceAttrFn } writer := os.Stderr @@ -83,7 +88,7 @@ func initializeSlog(logOpts LogOptions, useStdout bool) { } } -func replaceLevel(groups []string, a slog.Attr) slog.Attr { +func replaceAttrFn(groups []string, a slog.Attr) slog.Attr { switch a.Key { case slog.TimeKey: // Adjust to timestamp format that logrus uses; except that we can't @@ -95,21 +100,22 @@ func replaceLevel(groups []string, a slog.Attr) slog.Attr { Key: a.Key, Value: slog.StringValue(strings.ToLower(a.Value.String())), } + case logrErrorKey: + // Uniform the attribute identifying the error + return slog.Attr{ + Key: logfields.Error, + Value: a.Value, + } } return a } -func replaceLevelAndDropTime(groups []string, a slog.Attr) slog.Attr { +func replaceAttrFnWithoutTimestamp(groups []string, a slog.Attr) slog.Attr { switch a.Key { case slog.TimeKey: // Drop timestamps return slog.Attr{} - case slog.LevelKey: - // Lower-case the log level - return slog.Attr{ - Key: a.Key, - Value: slog.StringValue(strings.ToLower(a.Value.String())), - } + default: + return replaceAttrFn(groups, a) } - return a } diff --git a/vendor/github.com/cilium/cilium/pkg/mac/mac_linux.go b/vendor/github.com/cilium/cilium/pkg/mac/mac_linux.go index 9e9dd69ea7..8f0d415e7b 100644 --- a/vendor/github.com/cilium/cilium/pkg/mac/mac_linux.go +++ b/vendor/github.com/cilium/cilium/pkg/mac/mac_linux.go @@ -8,11 +8,13 @@ import ( "net" "github.com/vishvananda/netlink" + + "github.com/cilium/cilium/pkg/datapath/linux/safenetlink" ) // HasMacAddr returns true if the given network interface has L2 addr. func HasMacAddr(iface string) bool { - link, err := netlink.LinkByName(iface) + link, err := safenetlink.LinkByName(iface) if err != nil { return false } @@ -26,7 +28,7 @@ func LinkHasMacAddr(link netlink.Link) bool { // ReplaceMacAddressWithLinkName replaces the MAC address of the given link func ReplaceMacAddressWithLinkName(ifName, macAddress string) error { - l, err := netlink.LinkByName(ifName) + l, err := safenetlink.LinkByName(ifName) if err != nil { if errors.As(err, &netlink.LinkNotFoundError{}) { return nil diff --git a/vendor/github.com/cilium/cilium/pkg/monitor/api/files.go b/vendor/github.com/cilium/cilium/pkg/monitor/api/files.go index c2057b761f..b314a0582d 100644 --- a/vendor/github.com/cilium/cilium/pkg/monitor/api/files.go +++ b/vendor/github.com/cilium/cilium/pkg/monitor/api/files.go @@ -33,6 +33,7 @@ var files = map[uint8]string{ 112: "encap.h", 113: "encrypt.h", 114: "host_firewall.h", + 115: "nodeport_egress.h", // @@ source files list end } diff --git a/vendor/github.com/cilium/cilium/pkg/node/address.go b/vendor/github.com/cilium/cilium/pkg/node/address.go index 74225b98ae..8bfeea9948 100644 --- a/vendor/github.com/cilium/cilium/pkg/node/address.go +++ b/vendor/github.com/cilium/cilium/pkg/node/address.go @@ -262,12 +262,6 @@ func SetRouterInfo(info RouterInfo) { addrs.mu.Unlock() } -// GetHostMasqueradeIPv4 returns the IPv4 address to be used for masquerading -// any traffic that is being forwarded from the host into the Cilium cluster. -func GetHostMasqueradeIPv4() net.IP { - return GetInternalIPv4Router() -} - // SetIPv4AllocRange sets the IPv4 address pool to use when allocating // addresses for local endpoints func SetIPv4AllocRange(net *cidr.CIDR) { @@ -320,12 +314,6 @@ func GetIPv6() net.IP { return clone(n.GetNodeIP(true)) } -// GetHostMasqueradeIPv6 returns the IPv6 address to be used for masquerading -// any traffic that is being forwarded from the host into the Cilium cluster. -func GetHostMasqueradeIPv6() net.IP { - return GetIPv6Router() -} - // GetIPv6Router returns the IPv6 address of the router, e.g. address // of cilium_host device. func GetIPv6Router() net.IP { diff --git a/vendor/github.com/cilium/cilium/pkg/node/address_linux.go b/vendor/github.com/cilium/cilium/pkg/node/address_linux.go index 0f244f0bc0..b35e3261b0 100644 --- a/vendor/github.com/cilium/cilium/pkg/node/address_linux.go +++ b/vendor/github.com/cilium/cilium/pkg/node/address_linux.go @@ -14,6 +14,7 @@ import ( "github.com/vishvananda/netlink" "golang.org/x/sys/unix" + "github.com/cilium/cilium/pkg/datapath/linux/safenetlink" "github.com/cilium/cilium/pkg/ip" ) @@ -31,7 +32,7 @@ func firstGlobalAddr(intf string, preferredIP net.IP, family int, preferPublic b } if intf != "" && intf != "undefined" { - link, err = netlink.LinkByName(intf) + link, err = safenetlink.LinkByName(intf) if err != nil { link = nil } else { @@ -40,7 +41,7 @@ func firstGlobalAddr(intf string, preferredIP net.IP, family int, preferPublic b } retryInterface: - addr, err := netlink.AddrList(link, family) + addr, err := safenetlink.AddrList(link, family) if err != nil { return nil, err } @@ -167,11 +168,11 @@ func firstGlobalV6Addr(intf string, preferredIP net.IP, preferPublic bool) (net. // getCiliumHostIPsFromNetDev returns the first IPv4 link local and returns // it func getCiliumHostIPsFromNetDev(devName string) (ipv4GW, ipv6Router net.IP) { - hostDev, err := netlink.LinkByName(devName) + hostDev, err := safenetlink.LinkByName(devName) if err != nil { return nil, nil } - addrs, err := netlink.AddrList(hostDev, netlink.FAMILY_ALL) + addrs, err := safenetlink.AddrList(hostDev, netlink.FAMILY_ALL) if err != nil { return nil, nil } diff --git a/vendor/github.com/cilium/cilium/pkg/node/ip_linux.go b/vendor/github.com/cilium/cilium/pkg/node/ip_linux.go index f2078f774a..615d1c586a 100644 --- a/vendor/github.com/cilium/cilium/pkg/node/ip_linux.go +++ b/vendor/github.com/cilium/cilium/pkg/node/ip_linux.go @@ -7,6 +7,8 @@ import ( "strings" "github.com/vishvananda/netlink" + + "github.com/cilium/cilium/pkg/datapath/linux/safenetlink" ) func init() { @@ -18,7 +20,7 @@ func initExcludedIPs() { prefixes := []string{ "docker", } - links, err := netlink.LinkList() + links, err := safenetlink.LinkList() if err != nil { return } @@ -48,7 +50,7 @@ func initExcludedIPs() { continue } } - addr, err := netlink.AddrList(l, netlink.FAMILY_ALL) + addr, err := safenetlink.AddrList(l, netlink.FAMILY_ALL) if err != nil { continue } diff --git a/vendor/github.com/cilium/cilium/pkg/option/config.go b/vendor/github.com/cilium/cilium/pkg/option/config.go index ce10af41be..9611e85212 100644 --- a/vendor/github.com/cilium/cilium/pkg/option/config.go +++ b/vendor/github.com/cilium/cilium/pkg/option/config.go @@ -9,7 +9,6 @@ import ( "encoding/json" "errors" "fmt" - "io" "math" "net" "net/netip" @@ -31,10 +30,8 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/spf13/viper" - "google.golang.org/protobuf/types/known/fieldmaskpb" k8sLabels "k8s.io/apimachinery/pkg/labels" - flowpb "github.com/cilium/cilium/api/v1/flow" "github.com/cilium/cilium/api/v1/models" "github.com/cilium/cilium/pkg/cidr" clustermeshTypes "github.com/cilium/cilium/pkg/clustermesh/types" @@ -549,6 +546,9 @@ const ( // AuthMapEntriesDefault defines the default auth map limit. AuthMapEntriesDefault = 1 << 19 + // BPFConntrackAccounting controls whether CT accounting for packets and bytes is enabled + BPFConntrackAccountingDefault = false + // AuthMapEntriesName configures max entries for BPF auth map. AuthMapEntriesName = "bpf-auth-map-max" @@ -796,9 +796,6 @@ const ( // or direct routing is used and the node CIDR and pod CIDR overlap. EncryptionStrictModeAllowRemoteNodeIdentities = "encryption-strict-mode-allow-remote-node-identities" - // EnableWireguardUserspaceFallback is the name of the option that enables the fallback to WireGuard userspace mode - EnableWireguardUserspaceFallback = "enable-wireguard-userspace-fallback" - // WireguardPersistentKeepalivee controls Wireguard PersistentKeepalive option. Set 0 to disable. WireguardPersistentKeepalive = "wireguard-persistent-keepalive" @@ -969,141 +966,6 @@ const ( // PolicyAccountingArg argument enable policy accounting. PolicyAccountingArg = "policy-accounting" - // EnableHubble enables hubble in the agent. - EnableHubble = "enable-hubble" - - // HubbleSocketPath specifies the UNIX domain socket for Hubble server to listen to. - HubbleSocketPath = "hubble-socket-path" - - // HubbleListenAddress specifies address for Hubble server to listen to. - HubbleListenAddress = "hubble-listen-address" - - // HubblePreferIpv6 controls whether IPv6 or IPv4 addresses should be preferred for - // communication to agents, if both are available. - HubblePreferIpv6 = "hubble-prefer-ipv6" - - // HubbleTLSDisabled allows the Hubble server to run on the given listen - // address without TLS. - HubbleTLSDisabled = "hubble-disable-tls" - - // HubbleTLSCertFile specifies the path to the public key file for the - // Hubble server. The file must contain PEM encoded data. - HubbleTLSCertFile = "hubble-tls-cert-file" - - // HubbleTLSKeyFile specifies the path to the private key file for the - // Hubble server. The file must contain PEM encoded data. - HubbleTLSKeyFile = "hubble-tls-key-file" - - // HubbleTLSClientCAFiles specifies the path to one or more client CA - // certificates to use for TLS with mutual authentication (mTLS). The files - // must contain PEM encoded data. - HubbleTLSClientCAFiles = "hubble-tls-client-ca-files" - - // HubbleEventBufferCapacity specifies the capacity of Hubble events buffer. - HubbleEventBufferCapacity = "hubble-event-buffer-capacity" - - // HubbleEventQueueSize specifies the buffer size of the channel to receive monitor events. - HubbleEventQueueSize = "hubble-event-queue-size" - - // HubbleMetricsServer specifies the addresses to serve Hubble metrics on. - HubbleMetricsServer = "hubble-metrics-server" - - // HubbleMetricsTLSEnabled allows the Hubble metrics server to run on the given listen - // address with TLS. - HubbleMetricsTLSEnabled = "hubble-metrics-server-enable-tls" - - // HubbleMetricsServerTLSCertFile specifies the path to the public key file for the - // Hubble metrics server. The file must contain PEM encoded data. - HubbleMetricsTLSCertFile = "hubble-metrics-server-tls-cert-file" - - // HubbleMetricsServerTLSKeyFile specifies the path to the private key file for the - // Hubble metrics server. The file must contain PEM encoded data. - HubbleMetricsTLSKeyFile = "hubble-metrics-server-tls-key-file" - - // HubbleMetricsServerTLSClientCAFiles specifies the path to one or more client CA - // certificates to use for TLS with mutual authentication (mTLS) on the Hubble metrics server. - // The files must contain PEM encoded data. - HubbleMetricsTLSClientCAFiles = "hubble-metrics-server-tls-client-ca-files" - - // HubbleMetrics specifies enabled metrics and their configuration options. - HubbleMetrics = "hubble-metrics" - - // HubbleFlowlogsConfigFilePath specifies the filepath with configuration of hubble flowlogs. - // e.g. "/etc/cilium/flowlog.yaml" - HubbleFlowlogsConfigFilePath = "hubble-flowlogs-config-path" - - // HubbleExportFilePath specifies the filepath to write Hubble events to. - // e.g. "/var/run/cilium/hubble/events.log" - HubbleExportFilePath = "hubble-export-file-path" - - // HubbleExportFileMaxSizeMB specifies the file size in MB at which to rotate - // the Hubble export file. - HubbleExportFileMaxSizeMB = "hubble-export-file-max-size-mb" - - // HubbleExportFileMaxBacks specifies the number of rotated files to keep. - HubbleExportFileMaxBackups = "hubble-export-file-max-backups" - - // HubbleExportFileCompress specifies whether rotated files are compressed. - HubbleExportFileCompress = "hubble-export-file-compress" - - // HubbleExportAllowlist specifies allow list filter use by exporter. - HubbleExportAllowlist = "hubble-export-allowlist" - - // HubbleExportDenylist specifies deny list filter use by exporter. - HubbleExportDenylist = "hubble-export-denylist" - - // HubbleExportFieldmask specifies list of fields to log in exporter. - HubbleExportFieldmask = "hubble-export-fieldmask" - - // EnableHubbleRecorderAPI specifies if the Hubble Recorder API should be served - EnableHubbleRecorderAPI = "enable-hubble-recorder-api" - - // EnableHubbleOpenMetrics enables exporting hubble metrics in OpenMetrics format. - EnableHubbleOpenMetrics = "enable-hubble-open-metrics" - - // HubbleRecorderStoragePath specifies the directory in which pcap files - // created via the Hubble Recorder API are stored - HubbleRecorderStoragePath = "hubble-recorder-storage-path" - - // HubbleRecorderSinkQueueSize is the queue size for each recorder sink - HubbleRecorderSinkQueueSize = "hubble-recorder-sink-queue-size" - - // HubbleSkipUnknownCGroupIDs specifies if events with unknown cgroup ids should be skipped - HubbleSkipUnknownCGroupIDs = "hubble-skip-unknown-cgroup-ids" - - // HubbleMonitorEvents specifies Cilium monitor events for Hubble to observe. - // By default, Hubble observes all monitor events. - HubbleMonitorEvents = "hubble-monitor-events" - - // HubbleRedactEnabled controls if sensitive information will be redacted from L7 flows - HubbleRedactEnabled = "hubble-redact-enabled" - - // HubbleRedactHttpURLQuery controls if the URL query will be redacted from flows - HubbleRedactHttpURLQuery = "hubble-redact-http-urlquery" - - // HubbleRedactHttpUserInfo controls if the user info will be redacted from flows - HubbleRedactHttpUserInfo = "hubble-redact-http-userinfo" - - // HubbleRedactKafkaApiKey controls if the Kafka API key will be redacted from flows - HubbleRedactKafkaApiKey = "hubble-redact-kafka-apikey" - - // HubbleRedactHttpHeadersAllow controls which http headers will not be redacted from flows - HubbleRedactHttpHeadersAllow = "hubble-redact-http-headers-allow" - - // HubbleRedactHttpHeadersDeny controls which http headers will be redacted from flows - HubbleRedactHttpHeadersDeny = "hubble-redact-http-headers-deny" - - // HubbleDropEvents controls whether Hubble should create v1.Events - // for packet drops related to pods - HubbleDropEvents = "hubble-drop-events" - - // HubbleDropEventsInterval controls the minimum time between emitting events - // with the same source and destination IP - HubbleDropEventsInterval = "hubble-drop-events-interval" - - // HubbleDropEventsReasons controls which drop reasons to emit events for - HubbleDropEventsReasons = "hubble-drop-events-reasons" - // K8sClientConnectionTimeout configures the timeout for K8s client connections. K8sClientConnectionTimeout = "k8s-client-connection-timeout" @@ -1139,6 +1001,9 @@ const ( // LBAffinityMapMaxEntries configures max entries of bpf map for session affinity. LBAffinityMapMaxEntries = "bpf-lb-affinity-map-max" + // LBSourceRangeAllTypes configures service source ranges for all service types. + LBSourceRangeAllTypes = "bpf-lb-source-range-all-types" + // LBSourceRangeMapMaxEntries configures max entries of bpf map for service source ranges. LBSourceRangeMapMaxEntries = "bpf-lb-source-range-map-max" @@ -1237,6 +1102,13 @@ const ( // EnableK8sNetworkPolicy enables support for K8s NetworkPolicy. EnableK8sNetworkPolicy = "enable-k8s-networkpolicy" + // EnableCiliumNetworkPolicy enables support for Cilium Network Policy. + EnableCiliumNetworkPolicy = "enable-cilium-network-policy" + + // EnableCiliumClusterwideNetworkPolicy enables support for Cilium Clusterwide + // Network Policy. + EnableCiliumClusterwideNetworkPolicy = "enable-cilium-clusterwide-network-policy" + // PolicyCIDRMatchMode defines the entities that CIDR selectors can reach PolicyCIDRMatchMode = "policy-cidr-match-mode" @@ -1256,8 +1128,8 @@ const ( // BPFEventsTraceEnabled defines the TraceNotification setting for any endpoint BPFEventsTraceEnabled = "bpf-events-trace-enabled" - // BPFConntrackAccountingEnabled controls whether CT accounting for packets and bytes is enabled - BPFConntrackAccountingEnabled = "bpf-conntrack-accounting-enabled" + // BPFConntrackAccounting controls whether CT accounting for packets and bytes is enabled + BPFConntrackAccounting = "bpf-conntrack-accounting" // EnableInternalTrafficPolicy enables handling routing for services with internalTrafficPolicy configured EnableInternalTrafficPolicy = "enable-internal-traffic-policy" @@ -1314,6 +1186,9 @@ const ( // EnableExternalWorkloads enables the support for external workloads. EnableExternalWorkloads = "enable-external-workloads" + + // EnableSourceIPVerification enables the source ip verification, defaults to true + EnableSourceIPVerification = "enable-source-ip-verification" ) const ( @@ -1696,9 +1571,6 @@ type DaemonConfig struct { // or direct routing is used and the node CIDR and pod CIDR overlap. EncryptionStrictModeAllowRemoteNodeIdentities bool - // EnableWireguardUserspaceFallback enables the fallback to the userspace implementation - EnableWireguardUserspaceFallback bool - // WireguardPersistentKeepalive controls Wireguard PersistentKeepalive option. WireguardPersistentKeepalive time.Duration @@ -2147,141 +2019,6 @@ type DaemonConfig struct { // PolicyAccounting enable policy accounting PolicyAccounting bool - // EnableHubble specifies whether to enable the hubble server. - EnableHubble bool - - // HubbleSocketPath specifies the UNIX domain socket for Hubble server to listen to. - HubbleSocketPath string - - // HubbleListenAddress specifies address for Hubble to listen to. - HubbleListenAddress string - - // HubblePreferIpv6 controls whether IPv6 or IPv4 addresses should be preferred for - // communication to agents, if both are available. - HubblePreferIpv6 bool - - // HubbleTLSDisabled allows the Hubble server to run on the given listen - // address without TLS. - HubbleTLSDisabled bool - - // HubbleTLSCertFile specifies the path to the public key file for the - // Hubble server. The file must contain PEM encoded data. - HubbleTLSCertFile string - - // HubbleTLSKeyFile specifies the path to the private key file for the - // Hubble server. The file must contain PEM encoded data. - HubbleTLSKeyFile string - - // HubbleTLSClientCAFiles specifies the path to one or more client CA - // certificates to use for TLS with mutual authentication (mTLS). The files - // must contain PEM encoded data. - HubbleTLSClientCAFiles []string - - // HubbleEventBufferCapacity specifies the capacity of Hubble events buffer. - HubbleEventBufferCapacity int - - // HubbleEventQueueSize specifies the buffer size of the channel to receive monitor events. - HubbleEventQueueSize int - - // HubbleMetricsServer specifies the addresses to serve Hubble metrics on. - HubbleMetricsServer string - - // HubbleMetricsServerTLSEnabled allows the Hubble metrics server to run on the given listen - // address with TLS. - HubbleMetricsServerTLSEnabled bool - - // HubbleMetricsServerTLSCertFile specifies the path to the public key file for the - // Hubble server. The file must contain PEM encoded data. - HubbleMetricsServerTLSCertFile string - - // HubbleMetricsServerTLSKeyFile specifies the path to the private key file for the - // Hubble server. The file must contain PEM encoded data. - HubbleMetricsServerTLSKeyFile string - - // HubbleMetricsServerTLSClientCAFiles specifies the path to one or more client CA - // certificates to use for TLS with mutual authentication (mTLS). The files - // must contain PEM encoded data. - HubbleMetricsServerTLSClientCAFiles []string - - // HubbleMetrics specifies enabled metrics and their configuration options. - HubbleMetrics []string - - // HubbleFlowlogsConfigFilePath specifies the filepath with configuration of hubble flowlogs. - // e.g. "/etc/cilium/flowlog.yaml" - HubbleFlowlogsConfigFilePath string - - // HubbleExportFilePath specifies the filepath to write Hubble events to. - // e.g. "/var/run/cilium/hubble/events.log" - HubbleExportFilePath string - - // HubbleExportFileMaxSizeMB specifies the file size in MB at which to rotate - // the Hubble export file. - HubbleExportFileMaxSizeMB int - - // HubbleExportFileMaxBacks specifies the number of rotated files to keep. - HubbleExportFileMaxBackups int - - // HubbleExportFileCompress specifies whether rotated files are compressed. - HubbleExportFileCompress bool - - // HubbleExportAllowlist specifies allow list filter use by exporter. - HubbleExportAllowlist []*flowpb.FlowFilter - - // HubbleExportDenylist specifies deny list filter use by exporter. - HubbleExportDenylist []*flowpb.FlowFilter - - // HubbleExportFieldmask specifies list of fields to log in exporter. - HubbleExportFieldmask []string - - // EnableHubbleRecorderAPI specifies if the Hubble Recorder API should be served - EnableHubbleRecorderAPI bool - - // EnableHubbleOpenMetrics enables exporting hubble metrics in OpenMetrics format. - EnableHubbleOpenMetrics bool - - // HubbleRecorderStoragePath specifies the directory in which pcap files - // created via the Hubble Recorder API are stored - HubbleRecorderStoragePath string - - // HubbleRecorderSinkQueueSize is the queue size for each recorder sink - HubbleRecorderSinkQueueSize int - - // HubbleSkipUnknownCGroupIDs specifies if events with unknown cgroup ids should be skipped - HubbleSkipUnknownCGroupIDs bool - - // HubbleMonitorEvents specifies Cilium monitor events for Hubble to observe. - // By default, Hubble observes all monitor events. - HubbleMonitorEvents []string - - // HubbleRedactEnabled controls if Hubble will be redacting sensitive information from L7 flows - HubbleRedactEnabled bool - - // HubbleRedactURLQuery controls if the URL query will be redacted from flows - HubbleRedactHttpURLQuery bool - - // HubbleRedactUserInfo controls if the user info will be redacted from flows - HubbleRedactHttpUserInfo bool - - // HubbleRedactKafkaApiKey controls if Kafka API key will be redacted from flows - HubbleRedactKafkaApiKey bool - - // HubbleRedactHttpHeadersAllow controls which http headers will not be redacted from flows - HubbleRedactHttpHeadersAllow []string - - // HubbleRedactHttpHeadersDeny controls which http headers will be redacted from flows - HubbleRedactHttpHeadersDeny []string - - // HubbleDropEvents controls whether Hubble should create v1.Events - // for packet drops related to pods - HubbleDropEvents bool - - // HubbleDropEventsInterval controls the minimum time between emitting events - // with the same source and destination IP - HubbleDropEventsInterval time.Duration - - // HubbleDropEventsReasons controls which drop reasons to emit events for - HubbleDropEventsReasons []string - // EnableIPv4FragmentsTracking enables IPv4 fragments tracking for // L4-based lookups. Needs LRU map support. EnableIPv4FragmentsTracking bool @@ -2328,6 +2065,10 @@ type DaemonConfig struct { // LBAffinityMapEntries is the maximum number of entries allowed in BPF lbmap for session affinities. LBAffinityMapEntries int + // LBSourceRangeAllTypes enables propagation of loadbalancerSourceRanges to all Kubernetes + // service types which were created from the LoadBalancer service. + LBSourceRangeAllTypes bool + // LBSourceRangeMapEntries is the maximum number of entries allowed in BPF lbmap for source ranges. LBSourceRangeMapEntries int @@ -2443,8 +2184,8 @@ type DaemonConfig struct { // BPFEventsTraceEnabled controls whether the Cilium datapath exposes "trace" events to Cilium monitor and Hubble. BPFEventsTraceEnabled bool - // BPFConntrackAccountingEnabled controls whether CT accounting for packets and bytes is enabled. - BPFConntrackAccountingEnabled bool + // BPFConntrackAccounting controls whether CT accounting for packets and bytes is enabled. + BPFConntrackAccounting bool // IPAMCiliumNodeUpdateRate is the maximum rate at which the CiliumNode custom // resource is updated. @@ -2453,6 +2194,13 @@ type DaemonConfig struct { // EnableK8sNetworkPolicy enables support for K8s NetworkPolicy. EnableK8sNetworkPolicy bool + // EnableCiliumNetworkPolicy enables support for Cilium Network Policy. + EnableCiliumNetworkPolicy bool + + // EnableCiliumClusterwideNetworkPolicy enables support for Cilium Clusterwide + // Network Policy. + EnableCiliumClusterwideNetworkPolicy bool + // PolicyCIDRMatchMode is the list of entities that can be selected by CIDR policy. // Currently supported values: // - world @@ -2488,6 +2236,9 @@ type DaemonConfig struct { // EnableNonDefaultDenyPolicies allows policies to define whether they are operating in default-deny mode EnableNonDefaultDenyPolicies bool + + // EnableSourceIPVerification enables the source ip validation of connection from endpoints to endpoints + EnableSourceIPVerification bool } var ( @@ -2531,21 +2282,25 @@ var ( K8sEnableLeasesFallbackDiscovery: defaults.K8sEnableLeasesFallbackDiscovery, - ExternalClusterIP: defaults.ExternalClusterIP, - EnableVTEP: defaults.EnableVTEP, - EnableBGPControlPlane: defaults.EnableBGPControlPlane, - EnableK8sNetworkPolicy: defaults.EnableK8sNetworkPolicy, - PolicyCIDRMatchMode: defaults.PolicyCIDRMatchMode, - MaxConnectedClusters: defaults.MaxConnectedClusters, + ExternalClusterIP: defaults.ExternalClusterIP, + EnableVTEP: defaults.EnableVTEP, + EnableBGPControlPlane: defaults.EnableBGPControlPlane, + EnableK8sNetworkPolicy: defaults.EnableK8sNetworkPolicy, + EnableCiliumNetworkPolicy: defaults.EnableCiliumNetworkPolicy, + EnableCiliumClusterwideNetworkPolicy: defaults.EnableCiliumClusterwideNetworkPolicy, + PolicyCIDRMatchMode: defaults.PolicyCIDRMatchMode, + MaxConnectedClusters: defaults.MaxConnectedClusters, BPFEventsDropEnabled: defaults.BPFEventsDropEnabled, BPFEventsPolicyVerdictEnabled: defaults.BPFEventsPolicyVerdictEnabled, BPFEventsTraceEnabled: defaults.BPFEventsTraceEnabled, - BPFConntrackAccountingEnabled: defaults.BPFConntrackAccountingEnabled, + BPFConntrackAccounting: defaults.BPFConntrackAccounting, EnableEnvoyConfig: defaults.EnableEnvoyConfig, EnableInternalTrafficPolicy: defaults.EnableInternalTrafficPolicy, EnableNonDefaultDenyPolicies: defaults.EnableNonDefaultDenyPolicies, + + EnableSourceIPVerification: defaults.EnableSourceIPVerification, } ) @@ -2669,6 +2424,11 @@ func (c *DaemonConfig) IPv6Enabled() bool { return c.EnableIPv6 } +// LBProtoDiffEnabled returns true if LoadBalancerProtocolDifferentiation is enabled +func (c *DaemonConfig) LBProtoDiffEnabled() bool { + return c.LoadBalancerProtocolDifferentiation +} + // IPv6NDPEnabled returns true if IPv6 NDP support is enabled func (c *DaemonConfig) IPv6NDPEnabled() bool { return c.EnableIPv6NDP @@ -2798,13 +2558,6 @@ func (c *DaemonConfig) validateIPv6NAT46x64CIDR() error { return nil } -func (c *DaemonConfig) validateHubbleRedact() error { - if len(c.HubbleRedactHttpHeadersAllow) > 0 && len(c.HubbleRedactHttpHeadersDeny) > 0 { - return fmt.Errorf("Only one of --hubble-redact-http-headers-allow and --hubble-redact-http-headers-deny can be specified, not both") - } - return nil -} - func (c *DaemonConfig) validateContainerIPLocalReservedPorts() error { if c.ContainerIPLocalReservedPorts == "" || c.ContainerIPLocalReservedPorts == defaults.ContainerIPLocalReservedPortsAuto { return nil @@ -2829,10 +2582,6 @@ func (c *DaemonConfig) Validate(vp *viper.Viper) error { c.IPv6NAT46x64CIDR, err) } - if err := c.validateHubbleRedact(); err != nil { - return err - } - if c.MTU < 0 { return fmt.Errorf("MTU '%d' cannot be negative", c.MTU) } @@ -2869,7 +2618,7 @@ func (c *DaemonConfig) Validate(vp *viper.Viper) error { if err := cinfo.InitClusterIDMax(); err != nil { return err } - if err := cinfo.Validate(log); err != nil { + if err := cinfo.Validate(); err != nil { return err } @@ -3002,7 +2751,30 @@ func (c *DaemonConfig) parseExcludedLocalAddresses(s []string) error { return nil } -// Populate sets all options with the values from viper +// SetupLogging sets all logging-related options with the values from viper, +// then setup logging based on these options and the given tag. +// +// This allows initializing logging as early as possible, then log entries +// produced below in Populate can honor the requested logging configurations. +func (c *DaemonConfig) SetupLogging(vp *viper.Viper, tag string) { + c.Debug = vp.GetBool(DebugArg) + c.LogDriver = vp.GetStringSlice(LogDriver) + + if m, err := command.GetStringMapStringE(vp, LogOpt); err != nil { + log.Fatalf("unable to parse %s: %s", LogOpt, err) + } else { + c.LogOpt = m + } + + if err := logging.SetupLogging(c.LogDriver, logging.LogOptions(c.LogOpt), tag, c.Debug); err != nil { + log.Fatal(err) + } +} + +// Populate sets all non-logging options with the values from viper. +// +// This function may emit logs. Consider calling SetupLogging before this +// to make sure that they honor logging-related options. func (c *DaemonConfig) Populate(vp *viper.Viper) { var err error @@ -3022,7 +2794,6 @@ func (c *DaemonConfig) Populate(vp *viper.Viper) { c.ClusterName = vp.GetString(clustermeshTypes.OptClusterName) c.MaxConnectedClusters = vp.GetUint32(clustermeshTypes.OptMaxConnectedClusters) c.DatapathMode = vp.GetString(DatapathMode) - c.Debug = vp.GetBool(DebugArg) c.DebugVerbose = vp.GetStringSlice(DebugVerbose) c.EnableIPv4 = vp.GetBool(EnableIPv4Name) c.EnableIPv6 = vp.GetBool(EnableIPv6Name) @@ -3037,7 +2808,6 @@ func (c *DaemonConfig) Populate(vp *viper.Viper) { c.L2AnnouncerLeaseDuration = vp.GetDuration(L2AnnouncerLeaseDuration) c.L2AnnouncerRenewDeadline = vp.GetDuration(L2AnnouncerRenewDeadline) c.L2AnnouncerRetryPeriod = vp.GetDuration(L2AnnouncerRetryPeriod) - c.EnableWireguardUserspaceFallback = vp.GetBool(EnableWireguardUserspaceFallback) c.WireguardPersistentKeepalive = vp.GetDuration(WireguardPersistentKeepalive) c.EnableWellKnownIdentities = vp.GetBool(EnableWellKnownIdentities) c.EnableXDPPrefilter = vp.GetBool(EnableXDPPrefilter) @@ -3110,7 +2880,6 @@ func (c *DaemonConfig) Populate(vp *viper.Viper) { c.LabelPrefixFile = vp.GetString(LabelPrefixFile) c.Labels = vp.GetStringSlice(Labels) c.LibDir = vp.GetString(LibDir) - c.LogDriver = vp.GetStringSlice(LogDriver) c.LogSystemLoadConfig = vp.GetBool(LogSystemLoadConfigName) c.LoopbackIPv4 = vp.GetString(LoopbackIPv4) c.LocalRouterIPv4 = vp.GetString(LocalRouterIPv4) @@ -3173,8 +2942,9 @@ func (c *DaemonConfig) Populate(vp *viper.Viper) { c.BPFEventsDropEnabled = vp.GetBool(BPFEventsDropEnabled) c.BPFEventsPolicyVerdictEnabled = vp.GetBool(BPFEventsPolicyVerdictEnabled) c.BPFEventsTraceEnabled = vp.GetBool(BPFEventsTraceEnabled) - c.BPFConntrackAccountingEnabled = vp.GetBool(BPFConntrackAccountingEnabled) + c.BPFConntrackAccounting = vp.GetBool(BPFConntrackAccounting) c.EnableIPSecEncryptedOverlay = vp.GetBool(EnableIPSecEncryptedOverlay) + c.LBSourceRangeAllTypes = vp.GetBool(LBSourceRangeAllTypes) c.ServiceNoBackendResponse = vp.GetString(ServiceNoBackendResponse) switch c.ServiceNoBackendResponse { @@ -3397,12 +3167,6 @@ func (c *DaemonConfig) Populate(vp *viper.Viper) { c.KVStoreOpt = m } - if m, err := command.GetStringMapStringE(vp, LogOpt); err != nil { - log.Fatalf("unable to parse %s: %s", LogOpt, err) - } else { - c.LogOpt = m - } - bpfEventsDefaultRateLimit := vp.GetUint32(BPFEventsDefaultRateLimit) bpfEventsDefaultBurstLimit := vp.GetUint32(BPFEventsDefaultBurstLimit) switch { @@ -3483,82 +3247,6 @@ func (c *DaemonConfig) Populate(vp *viper.Viper) { } c.KubeProxyReplacementHealthzBindAddr = vp.GetString(KubeProxyReplacementHealthzBindAddr) - // Hubble options. - c.EnableHubble = vp.GetBool(EnableHubble) - c.EnableHubbleOpenMetrics = vp.GetBool(EnableHubbleOpenMetrics) - c.HubbleSocketPath = vp.GetString(HubbleSocketPath) - c.HubbleListenAddress = vp.GetString(HubbleListenAddress) - c.HubblePreferIpv6 = vp.GetBool(HubblePreferIpv6) - c.HubbleTLSDisabled = vp.GetBool(HubbleTLSDisabled) - c.HubbleTLSCertFile = vp.GetString(HubbleTLSCertFile) - c.HubbleTLSKeyFile = vp.GetString(HubbleTLSKeyFile) - c.HubbleTLSClientCAFiles = vp.GetStringSlice(HubbleTLSClientCAFiles) - c.HubbleEventBufferCapacity = vp.GetInt(HubbleEventBufferCapacity) - c.HubbleEventQueueSize = vp.GetInt(HubbleEventQueueSize) - if c.HubbleEventQueueSize == 0 { - c.HubbleEventQueueSize = getDefaultMonitorQueueSize(runtime.NumCPU()) - } - c.HubbleMetricsServer = vp.GetString(HubbleMetricsServer) - c.HubbleMetricsServerTLSEnabled = vp.GetBool(HubbleMetricsTLSEnabled) - c.HubbleMetricsServerTLSCertFile = vp.GetString(HubbleMetricsTLSCertFile) - c.HubbleMetricsServerTLSKeyFile = vp.GetString(HubbleMetricsTLSKeyFile) - c.HubbleMetricsServerTLSClientCAFiles = vp.GetStringSlice(HubbleMetricsTLSClientCAFiles) - c.HubbleMetrics = vp.GetStringSlice(HubbleMetrics) - - c.HubbleExportFilePath = vp.GetString(HubbleExportFilePath) - c.HubbleExportFileMaxSizeMB = vp.GetInt(HubbleExportFileMaxSizeMB) - c.HubbleExportFileMaxBackups = vp.GetInt(HubbleExportFileMaxBackups) - c.HubbleExportFileCompress = vp.GetBool(HubbleExportFileCompress) - - for _, enc := range vp.GetStringSlice(HubbleExportAllowlist) { - dec := json.NewDecoder(strings.NewReader(enc)) - var result flowpb.FlowFilter - if err := dec.Decode(&result); err != nil { - if errors.Is(err, io.EOF) { - break - } - log.Fatalf("failed to decode hubble-export-allowlist '%v': %s", enc, err) - } - c.HubbleExportAllowlist = append(c.HubbleExportAllowlist, &result) - } - - for _, enc := range vp.GetStringSlice(HubbleExportDenylist) { - dec := json.NewDecoder(strings.NewReader(enc)) - var result flowpb.FlowFilter - if err := dec.Decode(&result); err != nil { - if errors.Is(err, io.EOF) { - break - } - log.Fatalf("failed to decode hubble-export-denylist '%v': %s", enc, err) - } - c.HubbleExportDenylist = append(c.HubbleExportDenylist, &result) - } - - if fm := vp.GetStringSlice(HubbleExportFieldmask); len(fm) > 0 { - _, err := fieldmaskpb.New(&flowpb.Flow{}, fm...) - if err != nil { - log.Fatalf("hubble-export-fieldmask contains invalid fieldmask '%v': %s", fm, err) - } - c.HubbleExportFieldmask = vp.GetStringSlice(HubbleExportFieldmask) - } - - c.HubbleFlowlogsConfigFilePath = vp.GetString(HubbleFlowlogsConfigFilePath) - - c.EnableHubbleRecorderAPI = vp.GetBool(EnableHubbleRecorderAPI) - c.HubbleRecorderStoragePath = vp.GetString(HubbleRecorderStoragePath) - c.HubbleRecorderSinkQueueSize = vp.GetInt(HubbleRecorderSinkQueueSize) - c.HubbleSkipUnknownCGroupIDs = vp.GetBool(HubbleSkipUnknownCGroupIDs) - c.HubbleMonitorEvents = vp.GetStringSlice(HubbleMonitorEvents) - c.HubbleRedactEnabled = vp.GetBool(HubbleRedactEnabled) - c.HubbleRedactHttpURLQuery = vp.GetBool(HubbleRedactHttpURLQuery) - c.HubbleRedactHttpUserInfo = vp.GetBool(HubbleRedactHttpUserInfo) - c.HubbleRedactKafkaApiKey = vp.GetBool(HubbleRedactKafkaApiKey) - c.HubbleRedactHttpHeadersAllow = vp.GetStringSlice(HubbleRedactHttpHeadersAllow) - c.HubbleRedactHttpHeadersDeny = vp.GetStringSlice(HubbleRedactHttpHeadersDeny) - c.HubbleDropEvents = vp.GetBool(HubbleDropEvents) - c.HubbleDropEventsInterval = vp.GetDuration(HubbleDropEventsInterval) - c.HubbleDropEventsReasons = vp.GetStringSlice(HubbleDropEventsReasons) - // Hidden options c.CompilerFlags = vp.GetStringSlice(CompilerFlags) c.ConfigFile = vp.GetString(ConfigFile) @@ -3585,6 +3273,9 @@ func (c *DaemonConfig) Populate(vp *viper.Viper) { c.EnableNodeSelectorLabels = vp.GetBool(EnableNodeSelectorLabels) c.NodeLabels = vp.GetStringSlice(NodeLabels) + c.EnableCiliumNetworkPolicy = vp.GetBool(EnableCiliumNetworkPolicy) + c.EnableCiliumClusterwideNetworkPolicy = vp.GetBool(EnableCiliumClusterwideNetworkPolicy) + // Parse node label patterns nodeLabelPatterns := vp.GetStringSlice(ExcludeNodeLabelPatterns) for _, pattern := range nodeLabelPatterns { @@ -3603,6 +3294,8 @@ func (c *DaemonConfig) Populate(vp *viper.Viper) { c.LoadBalancerProtocolDifferentiation = vp.GetBool(LoadBalancerProtocolDifferentiation) c.EnableInternalTrafficPolicy = vp.GetBool(EnableInternalTrafficPolicy) + + c.EnableSourceIPVerification = vp.GetBool(EnableSourceIPVerification) } func (c *DaemonConfig) populateLoadBalancerSettings(vp *viper.Viper) { @@ -4327,14 +4020,6 @@ func InitConfig(cmd *cobra.Command, programName, configName string, vp *viper.Vi } } -func getDefaultMonitorQueueSize(numCPU int) int { - monitorQueueSize := numCPU * defaults.MonitorQueueSizePerCPU - if monitorQueueSize > defaults.MonitorQueueSizePerCPUMaximum { - monitorQueueSize = defaults.MonitorQueueSizePerCPUMaximum - } - return monitorQueueSize -} - // BPFEventBufferConfig contains parsed configuration for a bpf map event buffer. type BPFEventBufferConfig struct { Enabled bool diff --git a/vendor/github.com/cilium/cilium/pkg/policy/api/cidr.go b/vendor/github.com/cilium/cilium/pkg/policy/api/cidr.go index 68e900aaea..f377c41c5a 100644 --- a/vendor/github.com/cilium/cilium/pkg/policy/api/cidr.go +++ b/vendor/github.com/cilium/cilium/pkg/policy/api/cidr.go @@ -4,11 +4,10 @@ package api import ( - "net" "net/netip" "strings" - "github.com/cilium/cilium/pkg/ip" + slim_metav1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1" "github.com/cilium/cilium/pkg/labels" "github.com/cilium/cilium/pkg/option" ) @@ -22,6 +21,10 @@ type CIDR string var ( ipv4All = CIDR("0.0.0.0/0") ipv6All = CIDR("::/0") + + worldLabelNonDualStack = labels.Label{Source: labels.LabelSourceReserved, Key: labels.IDNameWorld} + worldLabelV4 = labels.Label{Source: labels.LabelSourceReserved, Key: labels.IDNameWorldIPv4} + worldLabelV6 = labels.Label{Source: labels.LabelSourceReserved, Key: labels.IDNameWorldIPv6} ) // CIDRRule is a rule that specifies a CIDR prefix to/from which outside @@ -136,52 +139,76 @@ type CIDRRuleSlice []CIDRRule // GetAsEndpointSelectors returns the provided CIDRRule slice as a slice of // endpoint selectors +// +// The ExceptCIDRs block is inserted as a negative match. Specifically, the +// DoesNotExist qualifier. For example, the CIDRRule +// +// cidr: 1.1.1.0/24 +// exceptCIDRs: ["1.1.1.1/32"] +// +// results in the selector equivalent to "cidr:1.1.1.0/24 !cidr:1.1.1.1/32". +// +// This works because the label selectors will select numeric identities belonging only +// to the shorter prefixes. However, longer prefixes will have a different numeric +// identity, as the bpf ipcache is an LPM lookup. This essentially acts as a +// "carve-out", using the LPM mechanism to exlude subsets of a larger prefix. func (s CIDRRuleSlice) GetAsEndpointSelectors() EndpointSelectorSlice { - cidrs := ComputeResultantCIDRSet(s) - ces := cidrs.GetAsEndpointSelectors() + ces := make(EndpointSelectorSlice, 0, len(s)) - // expand cidrgroup selectors for _, r := range s { - if r.CIDRGroupRef != "" { - ces = append(ces, NewESFromLabels(LabelForCIDRGroupRef(string(r.CIDRGroupRef)))) + ls := slim_metav1.LabelSelector{ + MatchExpressions: make([]slim_metav1.LabelSelectorRequirement, 0, 1+len(r.ExceptCIDRs)), } - } - - return ces -} - -// StringSlice returns the CIDRRuleSlice as a slice of strings. -func (s CIDRRuleSlice) StringSlice() []string { - result := make([]string, 0, len(s)) - for _, c := range s { - result = append(result, c.String()) - } - return result -} -// ComputeResultantCIDRSet converts a slice of CIDRRules into a slice of -// individual CIDRs. This expands the cidr defined by each CIDRRule, applies -// the CIDR exceptions defined in "ExceptCIDRs", and forms a minimal set of -// CIDRs that cover all of the CIDRRules. -// -// Assumes no error checking is necessary as CIDRRule.Sanitize already does this. -func ComputeResultantCIDRSet(cidrs CIDRRuleSlice) CIDRSlice { - var allResultantAllowedCIDRs CIDRSlice - for _, s := range cidrs { - _, allowNet, _ := net.ParseCIDR(string(s.Cidr)) - - var removeSubnets []*net.IPNet - for _, t := range s.ExceptCIDRs { - _, removeSubnet, _ := net.ParseCIDR(string(t)) - removeSubnets = append(removeSubnets, removeSubnet) + // add the "main" label: + // either a CIDR or CIDRGroupRef + if r.Cidr != "" { + var lbl labels.Label + switch r.Cidr { + case ipv4All: + if option.Config.IsDualStack() { + lbl = worldLabelV4 + } else { + lbl = worldLabelNonDualStack + } + case ipv6All: + if option.Config.IsDualStack() { + lbl = worldLabelV6 + } else { + lbl = worldLabelNonDualStack + } + default: + lbl, _ = labels.IPStringToLabel(string(r.Cidr)) + } + ls.MatchExpressions = append(ls.MatchExpressions, slim_metav1.LabelSelectorRequirement{ + Key: lbl.GetExtendedKey(), + Operator: slim_metav1.LabelSelectorOpExists, + }) + } else if r.CIDRGroupRef != "" { + lbl := LabelForCIDRGroupRef(string(r.CIDRGroupRef)) + ls.MatchExpressions = append(ls.MatchExpressions, slim_metav1.LabelSelectorRequirement{ + Key: lbl.GetExtendedKey(), + Operator: slim_metav1.LabelSelectorOpExists, + }) + } else { + // should never be hit, but paranoia + continue } - resultantAllowedCIDRs := ip.RemoveCIDRs([]*net.IPNet{allowNet}, removeSubnets) - for _, u := range resultantAllowedCIDRs { - allResultantAllowedCIDRs = append(allResultantAllowedCIDRs, CIDR(u.String())) + // exclude any excepted CIDRs. + // Do so by inserting a "DoesNotExist" requirement for the given prefix key + for _, exceptCIDR := range r.ExceptCIDRs { + lbl, _ := labels.IPStringToLabel(string(exceptCIDR)) + ls.MatchExpressions = append(ls.MatchExpressions, slim_metav1.LabelSelectorRequirement{ + Key: lbl.GetExtendedKey(), + Operator: slim_metav1.LabelSelectorOpDoesNotExist, + }) } + + ces = append(ces, NewESFromK8sLabelSelector("", &ls)) } - return allResultantAllowedCIDRs + + return ces } // addrsToCIDRRules generates CIDRRules for the IPs passed in. diff --git a/vendor/github.com/cilium/cilium/pkg/policy/api/rule_validation.go b/vendor/github.com/cilium/cilium/pkg/policy/api/rule_validation.go index 9c78f93faa..86cb890b20 100644 --- a/vendor/github.com/cilium/cilium/pkg/policy/api/rule_validation.go +++ b/vendor/github.com/cilium/cilium/pkg/policy/api/rule_validation.go @@ -28,6 +28,9 @@ var ( // Sanitize validates and sanitizes a policy rule. Minor edits such as // capitalization of the protocol name are automatically fixed up. More // fundamental violations will cause an error to be returned. +// +// Note: this function is called from both the operator and the agent; +// make sure any configuration flags are bound in **both** binaries. func (r *Rule) Sanitize() error { if option.Config.EnableNonDefaultDenyPolicies { // Fill in the default traffic posture of this Rule. @@ -218,13 +221,30 @@ func countNonGeneratedCIDRRules(s CIDRRuleSlice) int { return n } +// countNonGeneratedEndpoints counts the number of EndpointSelector items which are not +// `Generated`, i.e. were directly provided by the user. +// The `Generated` field is currently only set by the `ToServices` +// implementation, which extracts service endpoints and translates them as +// ToEndpoints rules before the CNP is passed to the policy repository. +// Therefore, we want to allow the combination of ToEndpoints and ToServices +// rules, if (and only if) the ToEndpoints only contains `Generated` entries. +func countNonGeneratedEndpoints(s []EndpointSelector) int { + n := 0 + for _, c := range s { + if !c.Generated { + n++ + } + } + return n +} + func (e *EgressRule) sanitize(hostPolicy bool) error { var retErr error l3Members := map[string]int{ "ToCIDR": len(e.ToCIDR), "ToCIDRSet": countNonGeneratedCIDRRules(e.ToCIDRSet), - "ToEndpoints": len(e.ToEndpoints), + "ToEndpoints": countNonGeneratedEndpoints(e.ToEndpoints), "ToEntities": len(e.ToEntities), "ToServices": len(e.ToServices), "ToFQDNs": len(e.ToFQDNs), @@ -236,7 +256,7 @@ func (e *EgressRule) sanitize(hostPolicy bool) error { "ToCIDRSet": true, "ToEndpoints": true, "ToEntities": true, - "ToServices": false, // see https://github.com/cilium/cilium/issues/20067 + "ToServices": true, "ToFQDNs": true, "ToGroups": true, "ToNodes": true, diff --git a/vendor/github.com/cilium/cilium/pkg/policy/api/selector.go b/vendor/github.com/cilium/cilium/pkg/policy/api/selector.go index c23aa0d9c2..7dc6c987fb 100644 --- a/vendor/github.com/cilium/cilium/pkg/policy/api/selector.go +++ b/vendor/github.com/cilium/cilium/pkg/policy/api/selector.go @@ -34,6 +34,10 @@ type EndpointSelector struct { // EndpointSelectors are created via `NewESFromMatchRequirements`. It is // immutable after its creation. cachedLabelSelectorString string `json:"-"` + + // Generated indicates whether the rule was generated based on other rules + // or provided by user + Generated bool `json:"-"` } // LabelSelectorString returns a user-friendly string representation of diff --git a/vendor/github.com/cilium/cilium/pkg/policy/api/zz_generated.deepequal.go b/vendor/github.com/cilium/cilium/pkg/policy/api/zz_generated.deepequal.go index ced0d973b7..557a0ba65a 100644 --- a/vendor/github.com/cilium/cilium/pkg/policy/api/zz_generated.deepequal.go +++ b/vendor/github.com/cilium/cilium/pkg/policy/api/zz_generated.deepequal.go @@ -414,6 +414,9 @@ func (in *EndpointSelector) DeepEqual(other *EndpointSelector) bool { if in.cachedLabelSelectorString != other.cachedLabelSelectorString { return false } + if in.Generated != other.Generated { + return false + } return true } @@ -1377,6 +1380,9 @@ func (in *ServiceSelector) DeepEqual(other *ServiceSelector) bool { if in.cachedLabelSelectorString != other.cachedLabelSelectorString { return false } + if in.Generated != other.Generated { + return false + } return true } diff --git a/vendor/github.com/cilium/cilium/pkg/policy/cidr.go b/vendor/github.com/cilium/cilium/pkg/policy/cidr.go index 33f14a9cd9..e854e677a0 100644 --- a/vendor/github.com/cilium/cilium/pkg/policy/cidr.go +++ b/vendor/github.com/cilium/cilium/pkg/policy/cidr.go @@ -8,6 +8,8 @@ import ( "github.com/cilium/cilium/pkg/ip" "github.com/cilium/cilium/pkg/policy/api" + + "k8s.io/apimachinery/pkg/util/sets" ) // getPrefixesFromCIDR fetches all CIDRs referred to by the specified slice @@ -18,58 +20,75 @@ func getPrefixesFromCIDR(cidrs api.CIDRSlice) []netip.Prefix { } // GetPrefixesFromCIDRSet fetches all CIDRs referred to by the specified slice -// and returns them as regular golang CIDR objects. +// and returns them as regular golang CIDR objects. Includes CIDRs listed in +// ExceptCIDRs fields. // // Assumes that validation already occurred on 'rules'. func GetPrefixesFromCIDRSet(rules api.CIDRRuleSlice) []netip.Prefix { - cidrs := api.ComputeResultantCIDRSet(rules) - return getPrefixesFromCIDR(cidrs) + out := make([]netip.Prefix, 0, len(rules)) + for _, rule := range rules { + if rule.Cidr != "" { + pfx, err := netip.ParsePrefix(string(rule.Cidr)) + if err == nil { + // must parse, was already validated. + out = append(out, pfx.Masked()) + } + } + for _, except := range rule.ExceptCIDRs { + pfx, err := netip.ParsePrefix(string(except)) + if err == nil { + out = append(out, pfx.Masked()) + } + } + } + + return out } // GetCIDRPrefixes runs through the specified 'rules' to find every reference // to a CIDR in the rules, and returns a slice containing all of these CIDRs. -// Multiple rules referring to the same CIDR will result in multiple copies of -// the CIDR in the returned slice. +// +// Includes prefixes referenced solely by "ExceptCIDRs" entries. // // Assumes that validation already occurred on 'rules'. func GetCIDRPrefixes(rules api.Rules) []netip.Prefix { if len(rules) == 0 { return nil } - res := make([]netip.Prefix, 0, 32) + res := make(sets.Set[netip.Prefix], 32) for _, r := range rules { for _, ir := range r.Ingress { if len(ir.FromCIDR) > 0 { - res = append(res, getPrefixesFromCIDR(ir.FromCIDR)...) + res.Insert(getPrefixesFromCIDR(ir.FromCIDR)...) } if len(ir.FromCIDRSet) > 0 { - res = append(res, GetPrefixesFromCIDRSet(ir.FromCIDRSet)...) + res.Insert(GetPrefixesFromCIDRSet(ir.FromCIDRSet)...) } } for _, ir := range r.IngressDeny { if len(ir.FromCIDR) > 0 { - res = append(res, getPrefixesFromCIDR(ir.FromCIDR)...) + res.Insert(getPrefixesFromCIDR(ir.FromCIDR)...) } if len(ir.FromCIDRSet) > 0 { - res = append(res, GetPrefixesFromCIDRSet(ir.FromCIDRSet)...) + res.Insert(GetPrefixesFromCIDRSet(ir.FromCIDRSet)...) } } for _, er := range r.Egress { if len(er.ToCIDR) > 0 { - res = append(res, getPrefixesFromCIDR(er.ToCIDR)...) + res.Insert(getPrefixesFromCIDR(er.ToCIDR)...) } if len(er.ToCIDRSet) > 0 { - res = append(res, GetPrefixesFromCIDRSet(er.ToCIDRSet)...) + res.Insert(GetPrefixesFromCIDRSet(er.ToCIDRSet)...) } } for _, er := range r.EgressDeny { if len(er.ToCIDR) > 0 { - res = append(res, getPrefixesFromCIDR(er.ToCIDR)...) + res.Insert(getPrefixesFromCIDR(er.ToCIDR)...) } if len(er.ToCIDRSet) > 0 { - res = append(res, GetPrefixesFromCIDRSet(er.ToCIDRSet)...) + res.Insert(GetPrefixesFromCIDRSet(er.ToCIDRSet)...) } } } - return res + return res.UnsortedList() } diff --git a/vendor/github.com/cilium/cilium/pkg/policy/distillery.go b/vendor/github.com/cilium/cilium/pkg/policy/distillery.go index ce55f54e6f..61b5822393 100644 --- a/vendor/github.com/cilium/cilium/pkg/policy/distillery.go +++ b/vendor/github.com/cilium/cilium/pkg/policy/distillery.go @@ -5,6 +5,7 @@ package policy import ( "fmt" + "iter" "sync/atomic" "github.com/cilium/cilium/pkg/container/versioned" @@ -17,9 +18,13 @@ import ( // the policy repository and ready to be distilled against a set of identities // to compute datapath-level policy configuration. type SelectorPolicy interface { + // CreateRedirects is used to ensure the endpoint has created all the needed redirects + // before a new EndpointPolicy is created. + RedirectFilters() iter.Seq2[*L4Filter, *PerSelectorPolicy] + // Consume returns the policy in terms of connectivity to peer // Identities. - Consume(owner PolicyOwner) *EndpointPolicy + Consume(owner PolicyOwner, redirects map[string]uint16) *EndpointPolicy } // PolicyCache represents a cache of resolved policies for identities. @@ -35,7 +40,7 @@ type PolicyCache struct { } // NewPolicyCache creates a new cache of SelectorPolicy. -func NewPolicyCache(repo *Repository, idmgr *identitymanager.IdentityManager) *PolicyCache { +func NewPolicyCache(repo *Repository, idmgr identitymanager.IDManager) *PolicyCache { cache := &PolicyCache{ repo: repo, policies: make(map[identityPkg.NumericIdentity]*cachedSelectorPolicy), @@ -229,10 +234,14 @@ func (cip *cachedSelectorPolicy) setPolicy(policy *selectorPolicy) { // // This denotes that a particular endpoint is 'consuming' the policy from the // selector policy cache. -func (cip *cachedSelectorPolicy) Consume(owner PolicyOwner) *EndpointPolicy { +func (cip *cachedSelectorPolicy) Consume(owner PolicyOwner, redirects map[string]uint16) *EndpointPolicy { // TODO: This currently computes the EndpointPolicy from SelectorPolicy // on-demand, however in future the cip is intended to cache the // EndpointPolicy for this Identity and emit datapath deltas instead. isHost := cip.identity.ID == identityPkg.ReservedIdentityHost - return cip.getPolicy().DistillPolicy(owner, isHost) + return cip.getPolicy().DistillPolicy(owner, redirects, isHost) +} + +func (cip *cachedSelectorPolicy) RedirectFilters() iter.Seq2[*L4Filter, *PerSelectorPolicy] { + return cip.getPolicy().RedirectFilters() } diff --git a/vendor/github.com/cilium/cilium/pkg/policy/l4.go b/vendor/github.com/cilium/cilium/pkg/policy/l4.go index b47c67fcb9..041bed8a65 100644 --- a/vendor/github.com/cilium/cilium/pkg/policy/l4.go +++ b/vendor/github.com/cilium/cilium/pkg/policy/l4.go @@ -12,7 +12,6 @@ import ( "strconv" "strings" "sync/atomic" - "testing" cilium "github.com/cilium/proxy/go/cilium/api" "github.com/sirupsen/logrus" @@ -60,8 +59,9 @@ func (l4rule *PerSelectorPolicy) covers(l3l4rule *PerSelectorPolicy) bool { if l3l4IsRedirect && !l4OnlyIsRedirect { // Can not skip if l3l4-rule is redirect while l4-only is not return false - } else if l3l4IsRedirect && l4OnlyIsRedirect && l3l4rule.Listener != l4rule.Listener { - // L3l4 rule has a different listener, it can not be skipped + } else if l3l4IsRedirect && l4OnlyIsRedirect && + (l3l4rule.Listener != l4rule.Listener || l3l4rule.Priority != l4rule.Priority) { + // L3l4 rule has a different listener or priority, it can not be skipped return false } @@ -504,7 +504,7 @@ func (l4 *L4Filter) GetPort() uint16 { } // Equals returns true if two L4Filters are equal -func (l4 *L4Filter) Equals(_ *testing.T, bL4 *L4Filter) bool { +func (l4 *L4Filter) Equals(bL4 *L4Filter) bool { if l4.Port == bL4.Port && l4.EndPort == bL4.EndPort && l4.PortName == bL4.PortName && @@ -529,14 +529,24 @@ func (l4 *L4Filter) Equals(_ *testing.T, bL4 *L4Filter) bool { } // ChangeState allows caller to revert changes made by (multiple) toMapState call(s) +// All fields are maps so we can pass this by value. type ChangeState struct { - Adds Keys // Added or modified keys, if not nil - Deletes Keys // deleted keys, if not nil - Old MapStateMap // Old values of all modified or deleted keys, if not nil + Adds Keys // Added or modified keys, if not nil + Deletes Keys // deleted keys, if not nil + old map[Key]mapStateEntry // Old values of all modified or deleted keys, if not nil +} + +// NewRevertState returns an empty ChangeState suitable for reverting MapState changes. +// The private 'old' field is initialized so that old state can be restored if need be. +func NewRevertState() ChangeState { + return ChangeState{ + Adds: make(Keys), + old: make(map[Key]mapStateEntry), + } } func (c *ChangeState) Empty() bool { - return len(c.Adds)+len(c.Deletes)+len(c.Old) == 0 + return len(c.Adds)+len(c.Deletes)+len(c.old) == 0 } // toMapState converts a single filter into a MapState entries added to 'p.PolicyMapState'. @@ -548,7 +558,7 @@ func (c *ChangeState) Empty() bool { // 'redirects' is the map of currently realized redirects, it is used to find the proxy port for any redirects. // p.SelectorCache is used as Identities interface during this call, which only has GetPrefix() that // needs no lock. -func (l4 *L4Filter) toMapState(p *EndpointPolicy, features policyFeatures, redirects map[string]uint16, changes ChangeState) { +func (l4 *L4Filter) toMapState(p *EndpointPolicy, features policyFeatures, changes ChangeState) { port := l4.Port proto := l4.U8Proto @@ -588,9 +598,10 @@ func (l4 *L4Filter) toMapState(p *EndpointPolicy, features policyFeatures, redir wildcardRule = l4.PerSelectorPolicies[l4.wildcard] } + isL4Wildcard := (l4.Port != 0 || l4.PortName != "") && l4.wildcard != nil for cs, currentRule := range l4.PerSelectorPolicies { // have wildcard and this is an L3L4 key? - isL3L4withWildcardPresent := (l4.Port != 0 || l4.PortName != "") && l4.wildcard != nil && cs != l4.wildcard + isL3L4withWildcardPresent := isL4Wildcard && cs != l4.wildcard if isL3L4withWildcardPresent && wildcardRule.covers(currentRule) { logger.WithField(logfields.EndpointSelector, cs).Debug("ToMapState: Skipping L3/L4 key due to existing L4-only key") @@ -600,6 +611,7 @@ func (l4 *L4Filter) toMapState(p *EndpointPolicy, features policyFeatures, redir isDenyRule := currentRule != nil && currentRule.IsDeny isRedirect := currentRule.IsRedirect() listener := currentRule.GetListener() + priority := currentRule.GetPriority() if !isDenyRule && isL3L4withWildcardPresent && !isRedirect { // Inherit the redirect status from the wildcard rule. // This is now needed as 'covers()' can pass non-redirect L3L4 rules @@ -608,27 +620,27 @@ func (l4 *L4Filter) toMapState(p *EndpointPolicy, features policyFeatures, redir // L4-only rule. isRedirect = wildcardRule.IsRedirect() listener = wildcardRule.GetListener() + priority = wildcardRule.GetPriority() } hasAuth, authType := currentRule.GetAuthType() var proxyPort uint16 if isRedirect { - var exists bool - proxyID := ProxyID(uint16(p.PolicyOwner.GetID()), l4.Ingress, string(l4.Protocol), port, listener) - proxyPort, exists = redirects[proxyID] - if !exists { + var err error + proxyPort, err = p.LookupRedirectPort(l4.Ingress, string(l4.Protocol), port, listener) + if err != nil { // Skip unrealized redirects; this happens routineously just // before new redirects are realized. Once created, we are called // again. - logger.WithField(logfields.EndpointSelector, cs).Debugf("Skipping unrealized redirect %s (%v)", proxyID, redirects) + logger.WithError(err).WithField(logfields.EndpointSelector, cs).Debugf("Skipping unrealized redirect") continue } } - entry := NewMapStateEntry(cs, l4.RuleOrigin[cs], proxyPort, currentRule.GetListener(), currentRule.GetPriority(), isDenyRule, hasAuth, authType) + entry := newMapStateEntry(cs, l4.RuleOrigin[cs], proxyPort, priority, isDenyRule, hasAuth, authType) if cs.IsWildcard() { for _, keyToAdd := range keysToAdd { keyToAdd.Identity = 0 - p.policyMapState.denyPreferredInsertWithChanges(keyToAdd, entry, features, changes) + p.policyMapState.insertWithChanges(keyToAdd, entry, features, changes) if port == 0 { // Allow-all @@ -660,15 +672,15 @@ func (l4 *L4Filter) toMapState(p *EndpointPolicy, features policyFeatures, redir for _, id := range idents { for _, keyToAdd := range keysToAdd { keyToAdd.Identity = id - p.policyMapState.denyPreferredInsertWithChanges(keyToAdd, entry, features, changes) + p.policyMapState.insertWithChanges(keyToAdd, entry, features, changes) // If Cilium is in dual-stack mode then the "World" identity // needs to be split into two identities to represent World // IPv6 and IPv4 traffic distinctly from one another. if id == identity.ReservedIdentityWorld && option.Config.IsDualStack() { keyToAdd.Identity = identity.ReservedIdentityWorldIPv4 - p.policyMapState.denyPreferredInsertWithChanges(keyToAdd, entry, features, changes) + p.policyMapState.insertWithChanges(keyToAdd, entry, features, changes) keyToAdd.Identity = identity.ReservedIdentityWorldIPv6 - p.policyMapState.denyPreferredInsertWithChanges(keyToAdd, entry, features, changes) + p.policyMapState.insertWithChanges(keyToAdd, entry, features, changes) } } } @@ -677,7 +689,7 @@ func (l4 *L4Filter) toMapState(p *EndpointPolicy, features policyFeatures, redir log.WithFields(logrus.Fields{ logfields.PolicyKeysAdded: changes.Adds, logfields.PolicyKeysDeleted: changes.Deletes, - logfields.PolicyEntriesOld: changes.Old, + logfields.PolicyEntriesOld: changes.old, }).Debug("ToMapChange changes") } } @@ -1106,7 +1118,7 @@ func addL4Filter(policyCtx PolicyContext, ctx *SearchContext, resMap L4PolicyMap, p api.PortProtocol, proto api.L4Proto, filterToMerge *L4Filter, - ruleLabels labels.LabelArray) error { +) error { existingFilter := resMap.ExactLookup(p.Port, uint16(p.EndPort), string(proto)) if existingFilter == nil { @@ -1144,16 +1156,17 @@ type L4PolicyMap interface { IngressCoversContext(ctx *SearchContext) api.Decision EgressCoversContext(ctx *SearchContext) api.Decision ForEach(func(l4 *L4Filter) bool) - Equals(t *testing.T, bMap L4PolicyMap) bool - Diff(t *testing.T, expectedMap L4PolicyMap) string + TestingOnlyEquals(bMap L4PolicyMap) bool + TestingOnlyDiff(expectedMap L4PolicyMap) string Len() int } // NewL4PolicyMap creates an new L4PolicMap. func NewL4PolicyMap() L4PolicyMap { return &l4PolicyMap{ - namedPortMap: make(map[string]*L4Filter), - rangePortMap: bitlpm.NewUintTrie[uint32, map[portProtoKey]*L4Filter](), + namedPortMap: make(map[string]*L4Filter), + rangePortMap: make(map[portProtoKey]*L4Filter), + rangePortIndex: bitlpm.NewUintTrie[uint32, map[portProtoKey]struct{}](), } } @@ -1161,8 +1174,9 @@ func NewL4PolicyMap() L4PolicyMap { // set of values. The initMap argument does not support port ranges. func NewL4PolicyMapWithValues(initMap map[string]*L4Filter) L4PolicyMap { l4M := &l4PolicyMap{ - namedPortMap: make(map[string]*L4Filter), - rangePortMap: bitlpm.NewUintTrie[uint32, map[portProtoKey]*L4Filter](), + namedPortMap: make(map[string]*L4Filter), + rangePortMap: make(map[portProtoKey]*L4Filter), + rangePortIndex: bitlpm.NewUintTrie[uint32, map[portProtoKey]struct{}](), } for k, v := range initMap { portProtoSlice := strings.Split(k, "/") @@ -1186,16 +1200,13 @@ type l4PolicyMap struct { // level, because they can only be resolved at the endpoint/identity // level. Named ports cannot have ranges. namedPortMap map[string]*L4Filter - // rangePortMap has to keep a map of L4Filters rather than - // a single L4Filter reference so that the l4PolicyMap does - // not merge L4Filter that are not the same port range, but - // share an overlapping range in the trie. - rangePortMap *bitlpm.UintTrie[uint32, map[portProtoKey]*L4Filter] - // rangeMapLen counts the number of unique L4Filters in - // the rangePortMap. It must be tracked separately from - // rangePortMap as L4Filters are split up when - // the port range length is not a power of two. - rangeMapLen int + // rangePortMap is a map of all L4Filters indexed by their port- + // protocol. + rangePortMap map[portProtoKey]*L4Filter + // rangePortIndex is an index of all L4Filters so that + // L4Filters that have overlapping port ranges can be looked up + // by with a single port. + rangePortIndex *bitlpm.UintTrie[uint32, map[portProtoKey]struct{}] } func parsePortProtocol(port, protocol string) (uint16, uint8) { @@ -1226,22 +1237,21 @@ func (l4M *l4PolicyMap) Upsert(port string, endPort uint16, protocol string, l4 endPort: endPort, proto: protoU, } - var upsertHappened bool - for _, mp := range PortRangeToMaskedPorts(portU, endPort) { - k := makePolicyMapKey(mp.port, mp.mask, protoU) - prefix := 32 - uint(bits.TrailingZeros16(mp.mask)) - portProtoMap, ok := l4M.rangePortMap.ExactLookup(prefix, k) - if !ok { - portProtoMap = make(map[portProtoKey]*L4Filter) - l4M.rangePortMap.Upsert(prefix, k, portProtoMap) - } - if !upsertHappened { - if _, ok := portProtoMap[ppK]; !ok { - l4M.rangeMapLen += 1 - upsertHappened = true + _, indexExists := l4M.rangePortMap[ppK] + l4M.rangePortMap[ppK] = l4 + // We do not need to reindex a key that already exists, + // even if the filter changed. + if !indexExists { + for _, mp := range PortRangeToMaskedPorts(portU, endPort) { + k := makePolicyMapKey(mp.port, mp.mask, protoU) + prefix := 32 - uint(bits.TrailingZeros16(mp.mask)) + portProtoSet, ok := l4M.rangePortIndex.ExactLookup(prefix, k) + if !ok { + portProtoSet = make(map[portProtoKey]struct{}) + l4M.rangePortIndex.Upsert(prefix, k, portProtoSet) } + portProtoSet[ppK] = struct{}{} } - portProtoMap[ppK] = l4 } } @@ -1258,23 +1268,21 @@ func (l4M *l4PolicyMap) Delete(port string, endPort uint16, protocol string) { endPort: endPort, proto: protoU, } - var deleteHappened bool - for _, mp := range PortRangeToMaskedPorts(portU, endPort) { - k := makePolicyMapKey(mp.port, mp.mask, protoU) - prefix := 32 - uint(bits.TrailingZeros16(mp.mask)) - portProtoMap, ok := l4M.rangePortMap.ExactLookup(prefix, k) - if !ok { - return - } - if _, ok := portProtoMap[ppK]; ok { - delete(portProtoMap, ppK) - if !deleteHappened { - l4M.rangeMapLen -= 1 - deleteHappened = true + _, indexExists := l4M.rangePortMap[ppK] + delete(l4M.rangePortMap, ppK) + // Only delete the index if the key exists. + if indexExists { + for _, mp := range PortRangeToMaskedPorts(portU, endPort) { + k := makePolicyMapKey(mp.port, mp.mask, protoU) + prefix := 32 - uint(bits.TrailingZeros16(mp.mask)) + portProtoSet, ok := l4M.rangePortIndex.ExactLookup(prefix, k) + if !ok { + return + } + delete(portProtoSet, ppK) + if len(portProtoSet) == 0 { + l4M.rangePortIndex.Delete(prefix, k) } - } - if len(portProtoMap) == 0 { - l4M.rangePortMap.Delete(prefix, k) } } } @@ -1291,18 +1299,7 @@ func (l4M *l4PolicyMap) ExactLookup(port string, endPort uint16, protocol string endPort: endPort, proto: protoU, } - for _, mp := range PortRangeToMaskedPorts(portU, endPort) { - k := makePolicyMapKey(mp.port, mp.mask, protoU) - prefix := 32 - uint(bits.TrailingZeros16(mp.mask)) - portProtoMap, ok := l4M.rangePortMap.ExactLookup(prefix, k) - if !ok { - return nil - } - if l4, ok := portProtoMap[ppK]; ok { - return l4 - } - } - return nil + return l4M.rangePortMap[ppK] } // MatchesLabels checks if a given port, protocol, and labels matches @@ -1318,13 +1315,16 @@ func (l4M *l4PolicyMap) MatchesLabels(port, protocol string, labels labels.Label portU, protoU := parsePortProtocol(port, protocol) l4PortProtoKeys := make(map[portProtoKey]struct{}) - l4M.rangePortMap.Ancestors(32, makePolicyMapKey(portU, 0xffff, protoU), - func(_ uint, _ uint32, portProtoMap map[portProtoKey]*L4Filter) bool { - for k, v := range portProtoMap { - if _, ok := l4PortProtoKeys[k]; !ok { - match, isDeny = v.matchesLabels(labels) - if isDeny { - return false + l4M.rangePortIndex.Ancestors(32, makePolicyMapKey(portU, 0xffff, protoU), + func(_ uint, _ uint32, portProtoSet map[portProtoKey]struct{}) bool { + for k := range portProtoSet { + v, ok := l4M.rangePortMap[k] + if ok { + if _, ok := l4PortProtoKeys[k]; !ok { + match, isDeny = v.matchesLabels(labels) + if isDeny { + return false + } } } } @@ -1336,23 +1336,19 @@ func (l4M *l4PolicyMap) MatchesLabels(port, protocol string, labels labels.Label // ForEach iterates over all L4Filters in the l4PolicyMap. func (l4M *l4PolicyMap) ForEach(fn func(l4 *L4Filter) bool) { for _, f := range l4M.namedPortMap { - fn(f) + if !fn(f) { + return + } } - l4PortProtoKeys := make(map[portProtoKey]struct{}) - l4M.rangePortMap.ForEach(func(prefix uint, key uint32, portPortoMap map[portProtoKey]*L4Filter) bool { - for k, v := range portPortoMap { - // We check for redundant L4Filters, because we split them apart in the index. - if _, ok := l4PortProtoKeys[k]; !ok { - fn(v) - l4PortProtoKeys[k] = struct{}{} - } + for _, v := range l4M.rangePortMap { + if !fn(v) { + return } - return true - }) + } } // Equals returns true if both L4PolicyMaps are equal. -func (l4M *l4PolicyMap) Equals(_ *testing.T, bMap L4PolicyMap) bool { +func (l4M *l4PolicyMap) TestingOnlyEquals(bMap L4PolicyMap) bool { if l4M.Len() != bMap.Len() { return false } @@ -1363,14 +1359,14 @@ func (l4M *l4PolicyMap) Equals(_ *testing.T, bMap L4PolicyMap) bool { port = fmt.Sprintf("%d", l4.Port) } l4B := bMap.ExactLookup(port, l4.EndPort, string(l4.Protocol)) - equal = l4.Equals(nil, l4B) + equal = l4.Equals(l4B) return equal }) return equal } // Diff returns the difference between to L4PolicyMaps. -func (l4M *l4PolicyMap) Diff(_ *testing.T, expected L4PolicyMap) (res string) { +func (l4M *l4PolicyMap) TestingOnlyDiff(expected L4PolicyMap) (res string) { res += "Missing (-), Unexpected (+):\n" expected.ForEach(func(eV *L4Filter) bool { port := eV.PortName @@ -1379,7 +1375,7 @@ func (l4M *l4PolicyMap) Diff(_ *testing.T, expected L4PolicyMap) (res string) { } oV := l4M.ExactLookup(port, eV.Port, string(eV.Protocol)) if oV != nil { - if !eV.Equals(nil, oV) { + if !eV.Equals(oV) { res += "- " + eV.String() + "\n" res += "+ " + oV.String() + "\n" } @@ -1407,7 +1403,7 @@ func (l4M *l4PolicyMap) Len() int { if l4M == nil { return 0 } - return len(l4M.namedPortMap) + l4M.rangeMapLen + return len(l4M.namedPortMap) + len(l4M.rangePortMap) } type policyFeatures uint8 @@ -1631,14 +1627,18 @@ func (l4Policy *L4Policy) AccumulateMapChanges(l4 *L4Filter, cs CachedSelector, var proxyPort uint16 if redirect { var err error - proxyPort, err = epPolicy.PolicyOwner.LookupRedirectPort(l4.Ingress, string(l4.Protocol), port, listener) + proxyPort, err = epPolicy.LookupRedirectPort(l4.Ingress, string(l4.Protocol), port, listener) if err != nil { - // This happens for new redirects that have not been realized - // yet. The accumulated changes should only be consumed after new - // redirects have been realized. ConsumeMapChanges then maps this - // invalid value to the real redirect port before the entry is - // visible to the endpoint package. - proxyPort = unrealizedRedirectPort + log.WithFields(logrus.Fields{ + logfields.EndpointSelector: cs, + logfields.Port: port, + logfields.Protocol: proto, + logfields.TrafficDirection: direction, + logfields.IsRedirect: redirect, + logfields.Listener: listener, + logfields.ListenerPriority: priority, + }).Warn("AccumulateMapChanges: Missing redirect.") + continue } } var keysToAdd []Key @@ -1646,7 +1646,7 @@ func (l4Policy *L4Policy) AccumulateMapChanges(l4 *L4Filter, cs CachedSelector, keysToAdd = append(keysToAdd, KeyForDirection(direction).WithPortProtoPrefix(proto, mp.port, uint8(bits.LeadingZeros16(^mp.mask)))) } - value := NewMapStateEntry(cs, derivedFrom, proxyPort, listener, priority, isDeny, hasAuth, authType) + value := newMapStateEntry(cs, derivedFrom, proxyPort, priority, isDeny, hasAuth, authType) if option.Config.Debug { authString := "default" @@ -1666,7 +1666,7 @@ func (l4Policy *L4Policy) AccumulateMapChanges(l4 *L4Filter, cs CachedSelector, logfields.ListenerPriority: priority, }).Debug("AccumulateMapChanges") } - epPolicy.policyMapChanges.AccumulateMapChanges(cs, adds, deletes, keysToAdd, value) + epPolicy.policyMapChanges.AccumulateMapChanges(adds, deletes, keysToAdd, value) } } @@ -1674,6 +1674,7 @@ func (l4Policy *L4Policy) AccumulateMapChanges(l4 *L4Filter, cs CachedSelector, func (l4Policy *L4Policy) SyncMapChanges(l4 *L4Filter, txn *versioned.Tx) { // SelectorCache may not be called into while holding this lock! l4Policy.mutex.RLock() + for epPolicy := range l4Policy.users { epPolicy.policyMapChanges.SyncMapChanges(txn) } diff --git a/vendor/github.com/cilium/cilium/pkg/policy/mapstate.go b/vendor/github.com/cilium/cilium/pkg/policy/mapstate.go index f9ac6d3d90..0fce54077d 100644 --- a/vendor/github.com/cilium/cilium/pkg/policy/mapstate.go +++ b/vendor/github.com/cilium/cilium/pkg/policy/mapstate.go @@ -5,6 +5,7 @@ package policy import ( "fmt" + "iter" "maps" "slices" "strconv" @@ -13,6 +14,7 @@ import ( "github.com/sirupsen/logrus" "github.com/cilium/cilium/pkg/container/bitlpm" + "github.com/cilium/cilium/pkg/container/set" "github.com/cilium/cilium/pkg/container/versioned" "github.com/cilium/cilium/pkg/identity" "github.com/cilium/cilium/pkg/labels" @@ -31,7 +33,10 @@ import ( // lest ye find yourself with hundreds of unnecessary imports. type Key = policyTypes.Key type Keys = policyTypes.Keys +type MapStateOwner = any // Key or CachedSelector +// Map type for external use. Internally we have more detail in private 'mapSteteEntry' type, +// as well as more extensive indexing via tries. type MapStateMap map[Key]MapStateEntry func EgressKey() policyTypes.Key { @@ -62,44 +67,14 @@ const ( LabelAllowLocalHostIngress = "allow-localhost-ingress" LabelAllowAnyIngress = "allow-any-ingress" LabelAllowAnyEgress = "allow-any-egress" - LabelVisibilityAnnotation = "visibility-annotation" - - // Using largest possible port value since it has the lowest priority - unrealizedRedirectPort = uint16(65535) ) -// MapState is a map interface for policy maps -type MapState interface { - Get(Key) (MapStateEntry, bool) - - // ForEach allows iteration over the MapStateEntries. It returns true if - // the iteration was not stopped early by the callback. - ForEach(func(Key, MapStateEntry) (cont bool)) (complete bool) - GetIdentities(*logrus.Logger) ([]int64, []int64) - GetDenyIdentities(*logrus.Logger) ([]int64, []int64) - Len() int - - // private accessors - deniesL4(policyOwner PolicyOwner, l4 *L4Filter) bool - - // - // modifiers are private - // - delete(Key) - insert(Key, MapStateEntry) - revertChanges(ChangeState) - - addVisibilityKeys(PolicyOwner, uint16, *VisibilityMetadata, ChangeState) - allowAllIdentities(ingress, egress bool) - determineAllowLocalhostIngress() - denyPreferredInsertWithChanges(newKey Key, newEntry MapStateEntry, features policyFeatures, changes ChangeState) - deleteKeyWithChanges(key Key, owner MapStateOwner, changes ChangeState) - - // For testing from other packages only - Equals(MapState) bool - Diff(expected MapState) string - WithState(initMap MapStateMap) MapState -} +var ( + LabelsAllowAnyIngress = labels.LabelArrayList{labels.LabelArray{ + labels.NewLabel(LabelKeyPolicyDerivedFrom, LabelAllowAnyIngress, labels.LabelSourceReserved)}} + LabelsAllowAnyEgress = labels.LabelArrayList{labels.LabelArray{ + labels.NewLabel(LabelKeyPolicyDerivedFrom, LabelAllowAnyEgress, labels.LabelSourceReserved)}} +) // mapState is a state of a policy map. type mapState struct { @@ -121,12 +96,12 @@ type mapState struct { // intermediate node in the Trie with its own sub node associated with // TrafficDirection, Protocol, and Port. When identity is not indexed // then one policy will map to one key-prefix with a builtin map type -// that associates each identity with a MapStateEntry. This strategy +// that associates each identity with a mapStateEntry. This strategy // greatly enhances the usefuleness of the Trie and improves lookup, // deletion, and insertion times. type mapStateMap struct { // entries is the map containing the MapStateEntries - entries MapStateMap + entries map[Key]mapStateEntry // trie is a Trie that indexes policy Keys without their identity // and stores the identities in an associated builtin map. trie bitlpm.Trie[bitlpm.Key[policyTypes.LPMKey], IDSet] @@ -134,12 +109,16 @@ type mapStateMap struct { type IDSet map[identity.NumericIdentity]struct{} -func (msm *mapStateMap) Lookup(k Key) (MapStateEntry, bool) { +func (msm *mapStateMap) Empty() bool { + return len(msm.entries) == 0 +} + +func (msm *mapStateMap) Lookup(k Key) (mapStateEntry, bool) { v, ok := msm.entries[k] return v, ok } -func (msm *mapStateMap) upsert(k Key, e MapStateEntry) { +func (msm *mapStateMap) upsert(k Key, e mapStateEntry) { _, exists := msm.entries[k] // upsert entry @@ -177,6 +156,15 @@ func (msm *mapStateMap) delete(k Key) { } func (msm *mapStateMap) ForEach(f func(Key, MapStateEntry) bool) bool { + for k, e := range msm.entries { + if !f(k, e.MapStateEntry) { + return false + } + } + return true +} + +func (msm *mapStateMap) forEach(f func(Key, mapStateEntry) bool) bool { for k, e := range msm.entries { if !f(k, e) { return false @@ -185,7 +173,7 @@ func (msm *mapStateMap) ForEach(f func(Key, MapStateEntry) bool) bool { return true } -func (msm *mapStateMap) forKey(k Key, f func(Key, MapStateEntry) bool) bool { +func (msm *mapStateMap) forKey(k Key, f func(Key, mapStateEntry) bool) bool { e, ok := msm.entries[k] if ok { return f(k, e) @@ -197,226 +185,214 @@ func (msm *mapStateMap) forKey(k Key, f func(Key, MapStateEntry) bool) bool { return true } -// ForEachNarrowerKeyWithBroaderID iterates over narrower port/proto's and broader IDs in the trie. -// Equal port/protos or identities are not included. -func (msm *mapStateMap) ForEachNarrowerKeyWithBroaderID(key Key, f func(Key, MapStateEntry) bool) { - msm.trie.Descendants(key.PrefixLength(), key, func(_ uint, lpmKey bitlpm.Key[policyTypes.LPMKey], idSet IDSet) bool { - // k is the key from trie with 0'ed ID - k := Key{ - LPMKey: lpmKey.Value(), - } - - // Descendants iterates over equal port/proto, caller expects to see only narrower keys so skip it - if k.PortProtoIsEqual(key) { - return true - } - - // ANY identities are ancestors of all - // identities, visit them first, but not if key is also ANY - if key.Identity != 0 { - if _, exists := idSet[0]; exists { - k.Identity = 0 - if !msm.forKey(k, f) { - return false - } - } +// forDifferentKeys calls 'f' for each Key 'k' with identities in 'idSet', if different from 'key'. +func (msm *mapStateMap) forDifferentKeys(key, k Key, idSet IDSet, f func(Key, mapStateEntry) bool) bool { + for id := range idSet { + k.Identity = id + if key != k && !msm.forKey(k, f) { + return false } - return true - }) + } + return true } -// ForEachBroaderOrEqualKey iterates over broader or equal keys in the trie. -func (msm *mapStateMap) ForEachBroaderOrEqualKey(key Key, f func(Key, MapStateEntry) bool) { - msm.trie.Ancestors(key.PrefixLength(), key, func(_ uint, lpmKey bitlpm.Key[policyTypes.LPMKey], idSet IDSet) bool { - // k is the key from trie with 0'ed ID - k := Key{ - LPMKey: lpmKey.Value(), - } - - // ANY identity is an ancestor of all identities, visit them first - if _, exists := idSet[0]; exists { - k.Identity = 0 +// forSpecificIDs calls 'f' for each non-ANY ID in 'idSet' with port/proto from 'k'. +func (msm *mapStateMap) forSpecificIDs(k Key, idSet IDSet, f func(Key, mapStateEntry) bool) bool { + for id := range idSet { + if id != 0 { + k.Identity = id if !msm.forKey(k, f) { return false } } + } + return true +} - // Need to visit all keys with the same identity - // ANY identity was already visited above - if key.Identity != 0 { - _, exists := idSet[key.Identity] - if exists { - k.Identity = key.Identity - if !msm.forKey(k, f) { - return false - } - } +// forIDs calls 'f' for each ID in 'idSet' with port/proto from 'k'. +func (msm *mapStateMap) forIDs(k Key, idSet IDSet, f func(Key, mapStateEntry) bool) bool { + for id := range idSet { + k.Identity = id + if !msm.forKey(k, f) { + return false } - return true - }) + } + return true } -func (msm *mapStateMap) forDescendantIDs(keyIdentity identity.NumericIdentity, k Key, idSet IDSet, f func(Key, MapStateEntry) bool) bool { - switch identity.NumericIdentity(keyIdentity) { - case identity.IdentityUnknown: // 0 - // All identities are descendants of ANY - for id := range idSet { - if id != 0 { - k.Identity = id - if !msm.forKey(k, f) { - return false - } - } +// forID calls 'f' for 'k' if 'k.Identity' exists in 'idSet'. +func (msm *mapStateMap) forID(k Key, idSet IDSet, f func(Key, mapStateEntry) bool) bool { + if _, exists := idSet[k.Identity]; exists { + if !msm.forKey(k, f) { + return false } } return true } -// ForEachNarrowerOrEqualKey iterates over narrower or equal keys in the trie. -func (msm *mapStateMap) ForEachNarrowerOrEqualKey(key Key, f func(Key, MapStateEntry) bool) { - msm.trie.Descendants(key.PrefixLength(), key, func(_ uint, lpmKey bitlpm.Key[policyTypes.LPMKey], idSet IDSet) bool { - // k is the key from trie with 0'ed ID - k := Key{ - LPMKey: lpmKey.Value(), - } +// NarrowerKeysWithWildcardID iterates over ANY keys with narrower port/proto's in the trie. +// Equal port/protos are not included. +// New keys with the protocol/port of the iterated keys can be safely added during iteration as this +// operation does not change the trie, but only adds elements to the idSet that is not used after +// yielding. +func (msm *mapStateMap) NarrowerKeysWithWildcardID(key Key) iter.Seq2[Key, mapStateEntry] { + return func(yield func(Key, mapStateEntry) bool) { + iter := msm.trie.DescendantIterator(key.PrefixLength(), key) + for ok, lpmKey, idSet := iter.Next(); ok; ok, lpmKey, idSet = iter.Next() { + k := Key{LPMKey: lpmKey.Value()} - // Need to visit all keys with the same identity - _, exists := idSet[key.Identity] - if exists { - k.Identity = key.Identity - if !msm.forKey(k, f) { - return false + // Visit narrower ANY keys + if !k.PortProtoIsEqual(key) && !msm.forID(k.WithIdentity(0), idSet, yield) { + return } } - - return msm.forDescendantIDs(key.Identity, k, idSet, f) - }) + } } -// ForEachBroaderKeyWithNarrowerID iterates over broader proto/port with narrower identity in the trie. -// Equal port/protos or identities are not included. -func (msm *mapStateMap) ForEachBroaderKeyWithNarrowerID(key Key, f func(Key, MapStateEntry) bool) { - msm.trie.Ancestors(key.PrefixLength(), key, func(_ uint, lpmKey bitlpm.Key[policyTypes.LPMKey], idSet IDSet) bool { - // k is the key from trie with 0'ed ID - k := Key{ - LPMKey: lpmKey.Value(), - } +// BroaderOrEqualKeys iterates over broader or equal (broader or equal port/proto and the same +// or wildcard ID) in the trie. +func (msm *mapStateMap) BroaderOrEqualKeys(key Key) iter.Seq2[Key, mapStateEntry] { + return func(yield func(Key, mapStateEntry) bool) { + iter := msm.trie.AncestorIterator(key.PrefixLength(), key) + for ok, lpmKey, idSet := iter.Next(); ok; ok, lpmKey, idSet = iter.Next() { + k := Key{LPMKey: lpmKey.Value()} - // Skip equal PortProto - if k.PortProtoIsEqual(key) { - return true - } + // ANY identity is broader or equal to all identities, visit it first if it exists + if !msm.forID(k.WithIdentity(0), idSet, yield) { + return + } - return msm.forDescendantIDs(key.Identity, k, idSet, f) - }) + // Visit key with the same identity, if it exists. + // ANY identity was already visited above. + if key.Identity != 0 && !msm.forID(k.WithIdentity(key.Identity), idSet, yield) { + return + } + } + } } -// ForEachBroaderOrEqualDatapathKey iterates over broader or equal keys in the trie. -// Visits all keys that datapath would match IF the 'key' was not added to the policy map. -// NOTE that CIDRs are not considered here as datapath does not support LPM matching in security IDs. -func (msm *mapStateMap) ForEachBroaderOrEqualDatapathKey(key Key, f func(Key, MapStateEntry) bool) { - msm.trie.Ancestors(key.PrefixLength(), key, func(_ uint, lpmKey bitlpm.Key[policyTypes.LPMKey], idSet IDSet) bool { - // k is the key from trie with 0'ed ID - k := Key{ - LPMKey: lpmKey.Value(), - } +// NarrowerKeys iterates over narrower keys in the trie. +func (msm *mapStateMap) NarrowerKeys(key Key) iter.Seq2[Key, mapStateEntry] { + return func(yield func(Key, mapStateEntry) bool) { + iter := msm.trie.DescendantIterator(key.PrefixLength(), key) + for ok, lpmKey, idSet := iter.Next(); ok; ok, lpmKey, idSet = iter.Next() { + k := Key{LPMKey: lpmKey.Value()} - // ANY identities are ancestors of all identities, visit them first - if _, exists := idSet[0]; exists { - k.Identity = 0 - if !msm.forKey(k, f) { - return false + // All identities are narrower than ANY identity, visit different keys + if key.Identity == 0 { + if !msm.forDifferentKeys(key, k, idSet, yield) { + return + } + } else { // key has a specific identity + // Need to visit the key with the same identity, if PortProto is different, + // and one exists. + if !k.PortProtoIsEqual(key) && !msm.forID(k.WithIdentity(key.Identity), idSet, yield) { + return + } } } + } +} - // Need to visit all keys with the same identity - // ANY identity was already visited above - if key.Identity != 0 { - _, exists := idSet[key.Identity] - if exists { - k.Identity = key.Identity - if !msm.forKey(k, f) { - return false +// NarrowerOrEqualKeys iterates over narrower or equal keys in the trie. +// Iterated keys can be safely deleted during iteration due to DescendantIterator holding enough +// state that allows iteration to be continued even if the current trie node is removed. +func (msm *mapStateMap) NarrowerOrEqualKeys(key Key) iter.Seq2[Key, mapStateEntry] { + return func(yield func(Key, mapStateEntry) bool) { + iter := msm.trie.DescendantIterator(key.PrefixLength(), key) + for ok, lpmKey, idSet := iter.Next(); ok; ok, lpmKey, idSet = iter.Next() { + k := Key{LPMKey: lpmKey.Value()} + + // All identities are narrower or equal to ANY identity. + if key.Identity == 0 { + if !msm.forIDs(k, idSet, yield) { + return + } + } else { // key has a specific identity + // Need to visit the key with the same identity, if it exists. + if !msm.forID(k.WithIdentity(key.Identity), idSet, yield) { + return } } } - return true - }) + } } -// ForEachNarrowerOrEqualDatapathKey iterates over narrower or equal keys in the trie. -// Visits all keys that datapath matches that would match 'key' if those keys were not in the policy map. -// NOTE that CIDRs are not considered here as datapath does not support LPM matching in security IDs. -func (msm *mapStateMap) ForEachNarrowerOrEqualDatapathKey(key Key, f func(Key, MapStateEntry) bool) { - msm.trie.Descendants(key.PrefixLength(), key, func(_ uint, lpmKey bitlpm.Key[policyTypes.LPMKey], idSet IDSet) bool { - // k is the key from trie with 0'ed ID - k := Key{ - LPMKey: lpmKey.Value(), +// BroaderKeysWithSpecificID iterates over keys with broader proto/port and a specific +// identity in the trie. +// Equal port/protos or identities are not included. +func (msm *mapStateMap) BroaderKeysWithSpecificID(key Key) iter.Seq2[Key, mapStateEntry] { + return func(yield func(Key, mapStateEntry) bool) { + iter := msm.trie.AncestorIterator(key.PrefixLength(), key) + for ok, lpmKey, idSet := iter.Next(); ok; ok, lpmKey, idSet = iter.Next() { + k := Key{LPMKey: lpmKey.Value()} + + // Visit different keys with specific IDs + if !k.PortProtoIsEqual(key) && !msm.forSpecificIDs(k, idSet, yield) { + return + } } + } +} + +// CoveringKeys iterates over broader port/proto entries in the trie in LPM order, +// with most specific match being returned first. +func (msm *mapStateMap) CoveringKeys(key Key) iter.Seq2[Key, mapStateEntry] { + return func(yield func(Key, mapStateEntry) bool) { + iter := msm.trie.AncestorLongestPrefixFirstIterator(key.PrefixLength(), key) + for ok, lpmKey, idSet := iter.Next(); ok; ok, lpmKey, idSet = iter.Next() { + k := Key{LPMKey: lpmKey.Value()} - // All identities are descendants of ANY identity. - if key.Identity == 0 { - for id := range idSet { - k.Identity = id - if !msm.forKey(k, f) { - return false + // Visit key with the same identity, if port/proto is different. + // ANY identity is visited below. + if key.Identity != 0 && !k.PortProtoIsEqual(key) { + if !msm.forID(k.WithIdentity(key.Identity), idSet, yield) { + return } } - } - // Need to visit all keys with the same identity. - // ANY identity was already visited above. - if key.Identity != 0 { - _, exists := idSet[key.Identity] - if exists { - k.Identity = key.Identity - if !msm.forKey(k, f) { - return false + // ANY identity covers all non-ANY identities, visit them second. + // Keys with ANY identity visit ANY keys only if port/proto is different. + if key.Identity != 0 || !k.PortProtoIsEqual(key) { + if !msm.forID(k.WithIdentity(0), idSet, yield) { + return } } } - return true - }) + } } -// ForEachKeyWithBroaderOrEqualPortProto iterates over broader or equal port/proto entries in the trie. -func (msm *mapStateMap) ForEachKeyWithBroaderOrEqualPortProto(key Key, f func(Key, MapStateEntry) bool) { - msm.trie.Ancestors(key.PrefixLength(), key, func(prefix uint, lpmKey bitlpm.Key[policyTypes.LPMKey], idSet IDSet) bool { - k := Key{ - LPMKey: lpmKey.Value(), - } - for id := range idSet { - k.Identity = id - if !msm.forKey(k, f) { - return false - } - } - return true - }) -} +// SubsetKeys iterates over narrower or equal port/proto entries in the trie in an LPM order +// (least specific match first). +func (msm *mapStateMap) SubsetKeys(key Key) iter.Seq2[Key, mapStateEntry] { + return func(yield func(Key, mapStateEntry) bool) { + iter := msm.trie.DescendantShortestPrefixFirstIterator(key.PrefixLength(), key) + for ok, lpmKey, idSet := iter.Next(); ok; ok, lpmKey, idSet = iter.Next() { + k := Key{LPMKey: lpmKey.Value()} -// ForEachKeyWithNarrowerOrEqualPortProto iterates over narrower or equal port/proto entries in the trie. -func (msm *mapStateMap) ForEachKeyWithNarrowerOrEqualPortProto(key Key, f func(Key, MapStateEntry) bool) { - msm.trie.Descendants(key.PrefixLength(), key, func(prefix uint, lpmKey bitlpm.Key[policyTypes.LPMKey], idSet IDSet) bool { - k := Key{ - LPMKey: lpmKey.Value(), - } - for id := range idSet { - k.Identity = id - if !msm.forKey(k, f) { - return false + // For an ANY key, visit all different keys + if key.Identity == 0 { + if !msm.forDifferentKeys(key, k, idSet, yield) { + return + } + } else { // key has a specific ID + // Visit only keys with the ANY or the same ID, if they exist + if !msm.forID(k.WithIdentity(0), idSet, yield) { + return + } + // Else visit the different key with the same identity + if !k.PortProtoIsEqual(key) && !msm.forID(k.WithIdentity(key.Identity), idSet, yield) { + return + } } } - return true - }) + } } func (msm *mapStateMap) Len() int { return len(msm.entries) } -type MapStateOwner interface{} - // MapStateEntry is the configuration associated with a Key in a // MapState. This is a minimized version of policymap.PolicyEntry. type MapStateEntry struct { @@ -425,64 +401,113 @@ type MapStateEntry struct { // Key. Any other value signifies proxy redirection. ProxyPort uint16 - // priority is used to select the Listener if multiple rules would apply different listeners - // to a policy map entry. Lower numbers indicate higher priority. If left out, the proxy - // port number (10000-20000) is used. - priority uint16 - - // Listener name for proxy redirection, if any - Listener string - // IsDeny is true when the policy should be denied. IsDeny bool - // hasAuthType is 'DefaultAuthType' when policy has no explicit AuthType set. In this case the - // value of AuthType is derived from more generic entries covering this entry. - hasAuthType HasAuthType - // AuthType is non-zero when authentication is required for the traffic to be allowed. AuthType AuthType +} - // DerivedFromRules tracks the policy rules this entry derives from +// mapSteteEntry is the entry type with additional internal bookkeping of the relation between +// explicitly and implicitly added entries. +type mapStateEntry struct { + MapStateEntry + + // priority is used to select the proxy port if multiple rules would apply different proxy + // ports to a policy map entry. Lower numbers indicate higher priority. If left out, the + // proxy port number (10000-20000) is used. + priority uint16 + + // hasAuthType is 'DefaultAuthType' when policy has no explicit AuthType set. In this case + // the value of AuthType is derived from more generic entries covering this entry. + hasAuthType HasAuthType + + // derivedFromRules tracks the policy rules this entry derives from. // In sorted order. - DerivedFromRules labels.LabelArrayList + derivedFromRules labels.LabelArrayList - // Owners collects the keys in the map and selectors in the policy that require this key to be present. + // owners collects the keys in the map and selectors in the policy that require this key to be present. // TODO: keep track which selector needed the entry to be deny, redirect, or just allow. - owners map[MapStateOwner]struct{} + owners set.Set[MapStateOwner] // dependents contains the keys for entries create based on this entry. These entries // will be deleted once all of the owners are deleted. dependents Keys } -// NewMapStateEntry creates a map state entry. If redirect is true, the +// newMapStateEntry creates a map state entry. If redirect is true, the // caller is expected to replace the ProxyPort field before it is added to // the actual BPF map. // 'cs' is used to keep track of which policy selectors need this entry. If it is 'nil' this entry // will become sticky and cannot be completely removed via incremental updates. Even in this case // the entry may be overridden or removed by a deny entry. -func NewMapStateEntry(cs MapStateOwner, derivedFrom labels.LabelArrayList, proxyPort uint16, listener string, priority uint16, deny bool, hasAuth HasAuthType, authType AuthType) MapStateEntry { +func newMapStateEntry(cs MapStateOwner, derivedFrom labels.LabelArrayList, proxyPort uint16, priority uint16, deny bool, hasAuth HasAuthType, authType AuthType) mapStateEntry { if proxyPort == 0 { - listener = "" priority = 0 } else if priority == 0 { priority = proxyPort // default for tie-breaking } - return MapStateEntry{ - ProxyPort: proxyPort, - Listener: listener, + return mapStateEntry{ + MapStateEntry: MapStateEntry{ + ProxyPort: proxyPort, + IsDeny: deny, + AuthType: authType, + }, priority: priority, - DerivedFromRules: derivedFrom, - IsDeny: deny, hasAuthType: hasAuth, - AuthType: authType, - owners: map[MapStateOwner]struct{}{cs: {}}, + derivedFromRules: derivedFrom, + owners: set.NewSet(cs), } } +// dependentOf returns a new mapStateEntry that is a copy of 'e', but has 'ownerKey' as the sole +// owner, and has no dependent keys. +func (e *mapStateEntry) dependentOf(ownerKey Key) mapStateEntry { + return mapStateEntry{ + MapStateEntry: e.MapStateEntry, + priority: e.priority, + hasAuthType: e.hasAuthType, + derivedFromRules: slices.Clone(e.derivedFromRules), + owners: set.NewSet[MapStateOwner](ownerKey), + } +} + +// dependentFrom returns a new mapStateEntry that is a copy of 'e', but has 'ownerKey' as the sole +// owner, and has no dependent keys. +func (e mapStateEntry) authOverrideFrom(ownerKey Key, entry *mapStateEntry) mapStateEntry { + lbls := slices.Clone(e.derivedFromRules) + lbls.MergeSorted(entry.derivedFromRules) + + return mapStateEntry{ + MapStateEntry: e.MapStateEntry.WithAuthType(entry.AuthType), + priority: e.priority, + hasAuthType: DefaultAuthType, + derivedFromRules: lbls, + owners: set.NewSet[MapStateOwner](ownerKey), + } +} + +func (e MapStateEntry) toMapStateEntry(priority uint16, hasAuth HasAuthType, cs MapStateOwner, derivedFrom labels.LabelArrayList) mapStateEntry { + if e.ProxyPort == 0 { + priority = 0 + } else if priority == 0 { + priority = e.ProxyPort // default for tie-breaking + } + return mapStateEntry{ + MapStateEntry: e, + priority: priority, + hasAuthType: hasAuth, + derivedFromRules: derivedFrom, + owners: set.NewSet(cs), + } +} + +func (e *mapStateEntry) GetRuleLabels() labels.LabelArrayList { + return e.derivedFromRules +} + // AddDependent adds 'key' to the set of dependent keys. -func (e *MapStateEntry) AddDependent(key Key) { +func (e *mapStateEntry) AddDependent(key Key) { if e.dependents == nil { e.dependents = make(Keys, 1) } @@ -490,7 +515,7 @@ func (e *MapStateEntry) AddDependent(key Key) { } // RemoveDependent removes 'key' from the set of dependent keys. -func (e *MapStateEntry) RemoveDependent(key Key) { +func (e *mapStateEntry) RemoveDependent(key Key) { delete(e.dependents, key) // Nil the map when empty. This is mainly to make unit testing easier. if len(e.dependents) == 0 { @@ -500,33 +525,14 @@ func (e *MapStateEntry) RemoveDependent(key Key) { // HasDependent returns true if the 'key' is contained // within the set of dependent keys -func (e *MapStateEntry) HasDependent(key Key) bool { - if e.dependents == nil { - return false - } +func (e *mapStateEntry) HasDependent(key Key) bool { _, ok := e.dependents[key] return ok } -// NewMapState creates a new MapState interface -func NewMapState() MapState { - return newMapState() -} - -func (ms *mapState) WithState(initMap MapStateMap) MapState { - return ms.withState(initMap) -} - -func (ms *mapState) withState(initMap MapStateMap) *mapState { - for k, v := range initMap { - ms.insert(k, v) - } - return ms -} - func newMapStateMap() mapStateMap { return mapStateMap{ - entries: make(MapStateMap), + entries: make(map[Key]mapStateEntry), trie: bitlpm.NewTrie[policyTypes.LPMKey, IDSet](policyTypes.MapStatePrefixLen), } } @@ -540,6 +546,15 @@ func newMapState() *mapState { // Get the MapStateEntry that matches the Key. func (ms *mapState) Get(k Key) (MapStateEntry, bool) { + v, ok := ms.get(k) + if ok { + return v.MapStateEntry, ok + } + return MapStateEntry{}, false +} + +// Get the mapStateEntry that matches the Key. +func (ms *mapState) get(k Key) (mapStateEntry, bool) { if k.DestPort == 0 && k.PortPrefixLen() > 0 { log.WithFields(logrus.Fields{ logfields.Stacktrace: hclog.Stacktrace(), @@ -553,14 +568,13 @@ func (ms *mapState) Get(k Key) (MapStateEntry, bool) { return ms.allows.Lookup(k) } -// insert the Key and matcthing MapStateEntry into the -// MapState -func (ms *mapState) insert(k Key, v MapStateEntry) { +// insert the Key and MapStateEntry into the MapState +func (ms *mapState) insert(k Key, v mapStateEntry) { if k.DestPort == 0 && k.PortPrefixLen() > 0 { log.WithFields(logrus.Fields{ logfields.Stacktrace: hclog.Stacktrace(), logfields.PolicyKey: k, - }).Errorf("mapState.Get: invalid port prefix length for wildcard port") + }).Errorf("mapState.insert: invalid port prefix length for wildcard port") } if v.IsDeny { ms.allows.delete(k) @@ -571,7 +585,27 @@ func (ms *mapState) insert(k Key, v MapStateEntry) { } } -// Delete removes the Key an related MapStateEntry. +// updateExisting re-inserts an existing entry to its map, to be used to persist changes in the +// entry. +// NOTE: Only to be used when Key and v.IsDeny has not been changed! +func (ms *mapState) updateExisting(k Key, v mapStateEntry) { + if v.IsDeny { + ms.denies.entries[k] = v + } else { + ms.allows.entries[k] = v + } +} + +// deleteExisting removes the Key an related MapStateEntry. +func (ms *mapState) deleteExisting(k Key, v mapStateEntry) { + if v.IsDeny { + ms.denies.delete(k) + } else { + ms.allows.delete(k) + } +} + +// delete removes the Key and related MapStateEntry. func (ms *mapState) delete(k Key) { ms.allows.delete(k) ms.denies.delete(k) @@ -583,32 +617,93 @@ func (ms *mapState) ForEach(f func(Key, MapStateEntry) (cont bool)) (complete bo return ms.allows.ForEach(f) && ms.denies.ForEach(f) } +// Empty returns 'true' if there are no entries in the map +func (ms *mapState) Empty() bool { + return ms.allows.Len() == 0 && ms.denies.Len() == 0 +} + +// forEach iterates over every Key MapStateEntry and stops when the function +// argument returns false. It returns false iff the iteration was cut short. +// Used for testing. +func (ms *mapState) forEach(f func(Key, mapStateEntry) (cont bool)) (complete bool) { + return ms.allows.forEach(f) && ms.denies.forEach(f) +} + // Len returns the length of the map func (ms *mapState) Len() int { return ms.allows.Len() + ms.denies.Len() } -// Equals determines if this MapState is equal to the -// argument MapState -// Only used for testing, but also from the endpoint package! -func (msA *mapState) Equals(msB MapState) bool { +// equalsWithLabels determines if this mapState is equal to the +// argument MapState. Only compares the exported MapStateEntry and derivedFromLabels. +// Only used for testing. +func (msA *mapState) equalsWithLabels(msB *mapState) bool { if msA.Len() != msB.Len() { return false } - return msA.ForEach(func(kA Key, vA MapStateEntry) bool { - vB, ok := msB.Get(kA) + return msA.forEach(func(kA Key, vA mapStateEntry) bool { + vB, ok := msB.get(kA) return ok && (&vB).DatapathAndDerivedFromEqual(&vA) }) } +// Equals determines if this MapState is equal to the +// argument (exported) MapStateMap +// Only used for testing from other packages. +func (msA *mapState) Equals(msB MapStateMap) bool { + if msA.Len() != len(msB) { + return false + } + return msA.forEach(func(kA Key, vA mapStateEntry) bool { + vB, ok := msB[kA] + return ok && vB == vA.MapStateEntry + }) +} + +// deepEquals determines if this MapState is equal to the argument MapState. +// Only used for testing. +func (msA *mapState) deepEquals(msB *mapState) bool { + if msA.Len() != msB.Len() { + return false + } + return msA.forEach(func(kA Key, vA mapStateEntry) bool { + vB, ok := msB.get(kA) + return ok && (&vB).deepEqual(&vA) + }) +} + // Diff returns the string of differences between 'obtained' and 'expected' prefixed with // '+ ' or '- ' for obtaining something unexpected, or not obtaining the expected, respectively. +// For use in debugging from other packages. +func (obtained *mapState) Diff(expected MapStateMap) (res string) { + res += "Missing (-), Unexpected (+):\n" + for kE, vE := range expected { + if vO, ok := obtained.get(kE); ok { + if vO.MapStateEntry != vE { + res += "- " + kE.String() + ": " + vE.String() + "\n" + res += "+ " + kE.String() + ": " + vO.MapStateEntry.String() + "\n" + } + } else { + res += "- " + kE.String() + ": " + vE.String() + "\n" + } + } + obtained.ForEach(func(kO Key, vO MapStateEntry) bool { + if _, ok := expected[kO]; !ok { + res += "+ " + kO.String() + ": " + vO.String() + "\n" + } + return true + }) + return res +} + +// diff returns the string of differences between 'obtained' and 'expected' prefixed with +// '+ ' or '- ' for obtaining something unexpected, or not obtaining the expected, respectively. // For use in debugging. -func (obtained *mapState) Diff(expected MapState) (res string) { +func (obtained *mapState) diff(expected *mapState) (res string) { res += "Missing (-), Unexpected (+):\n" - expected.ForEach(func(kE Key, vE MapStateEntry) bool { - if vO, ok := obtained.Get(kE); ok { - if !(&vO).DatapathAndDerivedFromEqual(&vE) { + expected.forEach(func(kE Key, vE mapStateEntry) bool { + if vO, ok := obtained.get(kE); ok { + if !(&vO).deepEqual(&vE) { res += "- " + kE.String() + ": " + vE.String() + "\n" res += "+ " + kE.String() + ": " + vO.String() + "\n" } @@ -617,9 +712,9 @@ func (obtained *mapState) Diff(expected MapState) (res string) { } return true }) - obtained.ForEach(func(kE Key, vE MapStateEntry) bool { - if _, ok := expected.Get(kE); !ok { - res += "+ " + kE.String() + ": " + vE.String() + "\n" + obtained.forEach(func(kO Key, vO mapStateEntry) bool { + if _, ok := expected.get(kO); !ok { + res += "+ " + kO.String() + ": " + vO.String() + "\n" } return true }) @@ -635,14 +730,13 @@ func (ms *mapState) AddDependent(owner Key, dependent Key, changes ChangeState) } } -// addDependentOnEntry adds 'dependent' to the set of dependent keys of 'e'. -func (ms *mapState) addDependentOnEntry(owner Key, e MapStateEntry, dependent Key, changes ChangeState) { +// addDependentOnEntry adds 'dependent' to the set of dependent keys of 'e', where 'e' already +// exists in 'ms'. +func (ms *mapState) addDependentOnEntry(owner Key, e mapStateEntry, dependent Key, changes ChangeState) { if _, exists := e.dependents[dependent]; !exists { - if changes.Old != nil { - changes.Old[owner] = e - } + changes.insertOldIfNotExists(owner, e) e.AddDependent(dependent) - ms.insert(owner, e) + ms.updateExisting(owner, e) } } @@ -665,17 +759,17 @@ func (ms *mapState) RemoveDependent(owner Key, dependent Key, changes ChangeStat } } -// Merge adds owners, dependents, and DerivedFromRules from a new 'entry' to an existing +// merge adds owners, dependents, and DerivedFromRules from a new 'entry' to an existing // entry 'e'. 'entry' is not modified. // Merge is only called if both entries are allow or deny entries, so deny precedence is not // considered here. // ProxyPort, and AuthType are merged by giving precedence to proxy redirection over no proxy // redirection, and explicit auth type over default auth type. -func (e *MapStateEntry) Merge(entry *MapStateEntry) { +func (e *mapStateEntry) merge(entry *mapStateEntry) { // Bail out loudly if both entries are not denies or allows if e.IsDeny != entry.IsDeny { log.WithField(logfields.Stacktrace, hclog.Stacktrace()). - Errorf("MapStateEntry.Merge: both entries must be allows or denies") + Errorf("MapStateEntry.merge: both entries must be allows or denies") return } // Only allow entries have proxy redirection or auth requirement @@ -687,7 +781,6 @@ func (e *MapStateEntry) Merge(entry *MapStateEntry) { // Proxy port value is the tie-breaker when priorities have the same value. if !e.IsRedirectEntry() || entry.priority < e.priority || entry.priority == e.priority && entry.ProxyPort < e.ProxyPort { e.ProxyPort = entry.ProxyPort - e.Listener = entry.Listener e.priority = entry.priority } } @@ -708,12 +801,7 @@ func (e *MapStateEntry) Merge(entry *MapStateEntry) { } } - if e.owners == nil && len(entry.owners) > 0 { - e.owners = make(map[MapStateOwner]struct{}, len(entry.owners)) - } - for k, v := range entry.owners { - e.owners[k] = v - } + e.owners.Merge(entry.owners) // merge dependents for k := range entry.dependents { @@ -721,8 +809,8 @@ func (e *MapStateEntry) Merge(entry *MapStateEntry) { } // merge DerivedFromRules - if len(entry.DerivedFromRules) > 0 { - e.DerivedFromRules.MergeSorted(entry.DerivedFromRules) + if len(entry.derivedFromRules) > 0 { + e.derivedFromRules.MergeSorted(entry.derivedFromRules) } } @@ -731,55 +819,37 @@ func (e *MapStateEntry) IsRedirectEntry() bool { return e.ProxyPort != 0 } -// DatapathEqual returns true of two entries are equal in the datapath's PoV, -// i.e., IsDeny, ProxyPort and AuthType are the same for both entries. -func (e *MapStateEntry) DatapathEqual(o *MapStateEntry) bool { - if e == nil || o == nil { - return e == o - } - - return e.IsDeny == o.IsDeny && e.ProxyPort == o.ProxyPort && e.AuthType == o.AuthType -} - // DatapathAndDerivedFromEqual returns true of two entries are equal in the datapath's PoV, // i.e., IsDeny, ProxyPort and AuthType are the same for both entries, and the DerivedFromRules // fields are also equal. // This is used for testing only via mapState.Equal and mapState.Diff. -func (e *MapStateEntry) DatapathAndDerivedFromEqual(o *MapStateEntry) bool { +func (e *mapStateEntry) DatapathAndDerivedFromEqual(o *mapStateEntry) bool { if e == nil || o == nil { return e == o } - return e.IsDeny == o.IsDeny && e.ProxyPort == o.ProxyPort && e.AuthType == o.AuthType && - e.DerivedFromRules.DeepEqual(&o.DerivedFromRules) + return e.MapStateEntry == o.MapStateEntry && e.derivedFromRules.DeepEqual(&o.derivedFromRules) } // DeepEqual is a manually generated deepequal function, deeply comparing the // receiver with other. in must be non-nil. // Defined manually due to deepequal-gen not supporting interface types. -// 'cachedNets' member is ignored in comparison, as it is a cached value and -// makes no functional difference. -func (e *MapStateEntry) DeepEqual(o *MapStateEntry) bool { - if !e.DatapathEqual(o) { +func (e *mapStateEntry) deepEqual(o *mapStateEntry) bool { + if e.MapStateEntry != o.MapStateEntry { return false } - if e.Listener != o.Listener || e.priority != o.priority { + if e.priority != o.priority { return false } - if !e.DerivedFromRules.DeepEqual(&o.DerivedFromRules) { + if !e.derivedFromRules.DeepEqual(&o.derivedFromRules) { return false } - if len(e.owners) != len(o.owners) { + if !e.owners.Equal(o.owners) { return false } - for k := range o.owners { - if _, exists := e.owners[k]; !exists { - return false - } - } if len(e.dependents) != len(o.dependents) { return false @@ -790,65 +860,64 @@ func (e *MapStateEntry) DeepEqual(o *MapStateEntry) bool { } } - // ignoring cachedNets - return true } +func (e MapStateEntry) WithAuthType(authType AuthType) MapStateEntry { + e.AuthType = authType + return e +} + // String returns a string representation of the MapStateEntry func (e MapStateEntry) String() string { return "ProxyPort=" + strconv.FormatUint(uint64(e.ProxyPort), 10) + - ",Listener=" + e.Listener + ",IsDeny=" + strconv.FormatBool(e.IsDeny) + - ",AuthType=" + e.AuthType.String() + - ",DerivedFromRules=" + fmt.Sprintf("%v", e.DerivedFromRules) + ",AuthType=" + e.AuthType.String() } -// denyPreferredInsert inserts a key and entry into the map by given preference -// to deny entries, and L3-only deny entries over L3-L4 allows. -// This form may be used when a full policy is computed and we are not yet interested -// in accumulating incremental changes. -// Caller may insert the same MapStateEntry multiple times for different Keys, but all from the same -// owner. -func (ms *mapState) denyPreferredInsert(newKey Key, newEntry MapStateEntry, features policyFeatures) { - // Enforce nil values from NewMapStateEntry - newEntry.dependents = nil - - ms.denyPreferredInsertWithChanges(newKey, newEntry, features, ChangeState{}) +// String returns a string representation of the MapStateEntry +func (e mapStateEntry) String() string { + return e.MapStateEntry.String() + + ",derivedFromRules=" + fmt.Sprintf("%v", e.derivedFromRules) + + ",priority=" + strconv.FormatUint(uint64(e.priority), 10) + + ",owners=" + e.owners.String() + + ",dependents=" + fmt.Sprintf("%v", e.dependents) } // addKeyWithChanges adds a 'key' with value 'entry' to 'keys' keeping track of incremental changes in 'adds' and 'deletes', and any changed or removed old values in 'old', if not nil. -func (ms *mapState) addKeyWithChanges(key Key, entry MapStateEntry, changes ChangeState) { +func (ms *mapState) addKeyWithChanges(key Key, entry mapStateEntry, changes ChangeState) bool { // Keep all owners that need this entry so that it is deleted only if all the owners delete their contribution var datapathEqual bool - oldEntry, exists := ms.Get(key) + oldEntry, exists := ms.get(key) // Only merge if both old and new are allows or denies if exists && (oldEntry.IsDeny == entry.IsDeny) { // Do nothing if entries are equal - if entry.DeepEqual(&oldEntry) { - return // nothing to do + if entry.deepEqual(&oldEntry) { + return false // nothing to do } // Save old value before any changes, if desired - if changes.Old != nil { - changes.insertOldIfNotExists(key, oldEntry) - } + changes.insertOldIfNotExists(key, oldEntry) // Compare for datapath equalness before merging, as the old entry is updated in // place! - datapathEqual = oldEntry.DatapathEqual(&entry) - - oldEntry.Merge(&entry) - ms.insert(key, oldEntry) + datapathEqual = oldEntry.MapStateEntry == entry.MapStateEntry + oldEntry.merge(&entry) + ms.updateExisting(key, oldEntry) } else if !exists || entry.IsDeny { // Insert a new entry if one did not exist or a deny entry is overwriting an allow // entry. - // Newly inserted entries must have their own containers, so that they - // remain separate when new owners/dependents are added to existing entries - entry.DerivedFromRules = slices.Clone(entry.DerivedFromRules) - entry.owners = maps.Clone(entry.owners) - entry.dependents = maps.Clone(entry.dependents) + + // Save old value before any changes, if any + if exists { + changes.insertOldIfNotExists(key, oldEntry) + } + + // Callers already have cloned the containers, no need to do it again here ms.insert(key, entry) + } else { + // Do not record and incremental add if nothing was done + return false } // Record an incremental Add if desired and entry is new or changed @@ -859,30 +928,38 @@ func (ms *mapState) addKeyWithChanges(key Key, entry MapStateEntry, changes Chan delete(changes.Deletes, key) } } + + return true } -// deleteKeyWithChanges deletes a 'key' from 'keys' keeping track of incremental changes in 'adds' and 'deletes'. -// The key is unconditionally deleted if 'cs' is nil, otherwise only the contribution of this 'cs' is removed. +// deleteKeyWithChanges deletes a 'key' from 'keys' keeping track of incremental changes in 'adds' +// and 'deletes'. +// The key is unconditionally deleted if 'owner' is nil, otherwise only the contribution of this +// 'owner' is removed. func (ms *mapState) deleteKeyWithChanges(key Key, owner MapStateOwner, changes ChangeState) { - if entry, exists := ms.Get(key); exists { + if entry, exists := ms.get(key); exists { // Save old value before any changes, if desired oldAdded := changes.insertOldIfNotExists(key, entry) if owner != nil { - // remove the contribution of the given selector only - if _, exists = entry.owners[owner]; exists { - // Remove the contribution of this selector from the entry - delete(entry.owners, owner) + if entry.owners.Has(owner) { + // remove this owner from entry's owners + changed := entry.owners.Remove(owner) + // Remove the dependency from the owner Key if ownerKey, ok := owner.(Key); ok { ms.RemoveDependent(ownerKey, key, changes) } // key is not deleted if other owners still need it - if len(entry.owners) > 0 { + if entry.owners.Len() > 0 { + if changed { + // re-insert entry due to owner change + ms.updateExisting(key, entry) + } return } } else { // 'owner' was not found, do not change anything if oldAdded { - delete(changes.Old, key) + delete(changes.old, key) } return } @@ -892,12 +969,8 @@ func (ms *mapState) deleteKeyWithChanges(key Key, owner MapStateOwner, changes C // Owner is nil when deleting more specific entries (e.g., L3/L4) when // adding deny entries that cover them (e.g., L3-deny). if owner == nil { - for owner := range entry.owners { - if owner != nil { - if ownerKey, ok := owner.(Key); ok { - ms.RemoveDependent(ownerKey, key, changes) - } - } + for ownerKey := range set.MembersOfType[Key](entry.owners) { + ms.RemoveDependent(ownerKey, key, changes) } } @@ -913,8 +986,8 @@ func (ms *mapState) deleteKeyWithChanges(key Key, owner MapStateOwner, changes C } } - ms.allows.delete(key) - ms.denies.delete(key) + // delete entry from the map it exists in + ms.deleteExisting(key, entry) } } @@ -926,321 +999,161 @@ func (ms *mapState) revertChanges(changes ChangeState) { ms.denies.delete(k) } // 'old' contains all the original values of both modified and deleted entries - for k, v := range changes.Old { + for k, v := range changes.old { ms.insert(k, v) } } -// denyPreferredInsertWithChanges contains the most important business logic for policy insertions. It inserts -// a key and entry into the map by giving preference to deny entries, and L3-only deny entries over L3-L4 allows. -// Incremental changes performed are recorded in 'adds' and 'deletes', if not nil. -// See https://docs.google.com/spreadsheets/d/1WANIoZGB48nryylQjjOw6lKjI80eVgPShrdMTMalLEw#gid=2109052536 for details -func (ms *mapState) denyPreferredInsertWithChanges(newKey Key, newEntry MapStateEntry, features policyFeatures, changes ChangeState) { - // Sanity check on the newKey - if newKey.TrafficDirection() >= trafficdirection.Invalid { - log.WithFields(logrus.Fields{ - logfields.Stacktrace: hclog.Stacktrace(), - logfields.TrafficDirection: newKey.TrafficDirection, - }).Errorf("mapState.denyPreferredInsertWithChanges: invalid traffic direction in key") - return +// insertDependentOfKey adds a dependent entry to 'k' with the more specific port/proto of 'newKey' +// to ensure 'v' takes precedence. +// Called only for 'k' with specific identity and with broader protocol/port than l4-only 'newKey'. +func (ms *mapState) insertDependentOfKey(k Key, v mapStateEntry, newKey Key, changes ChangeState) { + // Compute narrower 'key' with identity of 'k' + key := newKey.WithIdentity(k.Identity) + if ms.addKeyWithChanges(key, v.dependentOf(k), changes) { + ms.addDependentOnEntry(k, v, key, changes) } - // Skip deny rules processing if the policy in this direction has no deny rules - if !features.contains(denyRules) { - ms.authPreferredInsert(newKey, newEntry, features, changes) - return +} + +// insertDependentOfNewKey adds a dependent entry to 'newKey' with the more specific port/proto of +// 'k' to ensure 'newEntry' takes precedence. +// Called only for L4-only 'k' with narrower protocol/port than 'newKey' with a specific identity. +func (ms *mapState) insertDependentOfNewKey(newKey Key, newEntry *mapStateEntry, k Key, changes ChangeState) { + // Compute narrower 'key' with identity of 'newKey' + key := k.WithIdentity(newKey.Identity) + if ms.addKeyWithChanges(key, newEntry.dependentOf(newKey), changes) { + newEntry.AddDependent(key) } +} - // If we have a deny "all" we don't accept any kind of map entry. - if _, ok := ms.denies.Lookup(allKey[newKey.TrafficDirection()]); ok { - return +// insertAuthOverrideFromKey adds a dependent entry to 'k' with the more specific port/proto of +// 'newKey' and with override auth type from 'v' to ensure auth type of 'v' is used for identity of +// 'k' also when the traffic matches the L4-only 'newKey'. +// Called only for 'k' with specific identity and with broader protocol/port than L4-only 'newKey'. +func (ms *mapState) insertAuthOverrideFromKey(k Key, v mapStateEntry, newKey Key, newEntry mapStateEntry, changes ChangeState) { + // Compute narrower 'key' with identity of 'k' + key := newKey.WithIdentity(k.Identity) + if ms.addKeyWithChanges(key, newEntry.authOverrideFrom(k, &v), changes) { + ms.addDependentOnEntry(k, v, key, changes) } +} - // Since bpf datapath denies by default, we only need to add deny entries to carve out more - // specific holes to less specific allow rules. But since we don't if allow entries will be - // added later (e.g., incrementally due to FQDN rules), we must generally add deny entries - // even if there are no allow entries yet. - - // Datapath matches security IDs exactly, or completely wildcards them (ID == 0). Datapath - // has no LPM/CIDR logic for security IDs. We use LPM/CIDR logic here to find out if allow - // entries are "covered" by deny entries and change them to deny entries if so. We can not - // rely on the default deny as a broad allow could be added later. - - // We cannot update the map while we are - // iterating through it, so we record the - // changes to be made and then apply them. - // Additionally, we need to perform deletes - // first so that deny entries do not get - // merged with allows that are set to be - // deleted. - var ( - updates []MapChange - deletes []Key - ) - if newEntry.IsDeny { - // Test for bailed case first so that we avoid unnecessary computation if entry is - // not going to be added. - bailed := false - // If there is an ANY or equal deny key, then do not add a more specific one. - // A narrower of two deny keys is redundant in the datapath only if the broader ID - // is 0, or the IDs are the same. This is because the ID will be assigned from the - // ipcache and datapath has no notion of one ID being related to another. - ms.denies.ForEachBroaderOrEqualDatapathKey(newKey, func(k Key, v MapStateEntry) bool { - // Identical key needs to be added if the entries are different (to merge - // them). - if k != newKey || v.DeepEqual(&newEntry) { - // If the ID of this iterated-deny-entry is ANY or equal of - // the new-entry and the iterated-deny-entry has a broader (or - // equal) port-protocol then we need not insert the new entry. - bailed = true - return false +// insertAuthOverrideKey adds a dependent entry to 'newKey' with the more specific port/proto of 'k' +// and with override auth type from 'newEntry' to ensure auth type of 'newEntry' is used for +// identity of 'newKey' also when the traffic matches the L4-only 'k'. +// Called only for L4-only 'k' with narrower protocol/port than 'newKey' with a specific identity. +func (ms *mapState) insertAuthOverrideFromNewKey(newKey Key, newEntry *mapStateEntry, k Key, v mapStateEntry, changes ChangeState) { + // Compute narrower 'key' with identity of 'newKey' + key := k.WithIdentity(newKey.Identity) + if ms.addKeyWithChanges(key, v.authOverrideFrom(newKey, newEntry), changes) { + newEntry.AddDependent(key) + } +} + +func (ms *mapState) insertWithChanges(key Key, entry mapStateEntry, features policyFeatures, changes ChangeState) { + ms.denyPreferredInsertWithChanges(key, entry, features, changes) +} + +// denyPreferredInsertWithChanges contains the most important business logic for policy +// insertions. It inserts a key and entry into the map by giving preference to deny entries, and +// L3-only deny entries over L3-L4 allows. +// +// Since bpf datapath denies by default, we only need to add deny entries to carve out more specific +// holes to less specific allow rules. But since we don't if allow entries will be added later +// (e.g., incrementally due to FQDN rules), we must generally add deny entries even if there are no +// allow entries yet. +// +// Note on bailed or deleted entries: In general, if we bail out due to being covered by an existing +// entry, or delete an entry due to being covered by the new one, we would want this action reversed +// if the existing entry or this new one is incremantally removed, respectively. +// Generally, whenever a deny entry covers an allow entry (i.e., covering key has broader or equal +// protocol/port, and the keys have the same identity, or the covering key has wildcard identity (ID +// == 0)). +// Secondly, only keys with a specific identity (ID != 0) can be incrementally added or deleted. +// Finally, due to the selector cache being transactional, when an identity is removed, all keys +// with that identity are incrementally deleted. +// Hence, if a covering key is incrementally deleted, it is a key with a specific identity, and all +// keys covered by it will be deleted as well, so there is no situation where this bailed-out or +// deleted key should be reinstated due to the covering key being incrementally deleted. +// +// Note on added dependent L3/4 entries: Since the datapath always gives precedence to the matching +// entry with the most specific L4 (narrower protocol/port), we need to add L3/4 entries e.g., when +// precedence would be given to a narrower allow entry with the wildcard identity (L4-only key), +// while precedence should be given to the deny entry with a specific identity and broader L4 when +// the given packet matches both of them. To force the datapath to give precedence to the deny entry +// we add a new dependent deny entry with the identity of the (broader) deny entry and the L4 +// protocol and port of the (narrower) L4-only key. The added key is marked as a dependent entry of +// the key with a specific identity (rather than the l4-only key), so that the dependent added entry +// is also deleted when the identity of its owner key is (incrementally) removed. +// +// Incremental changes performed are recorded in 'changes'. +func (ms *mapState) denyPreferredInsertWithChanges(newKey Key, newEntry mapStateEntry, features policyFeatures, changes ChangeState) { + // Bail if covered by a deny key + if !ms.denies.Empty() { + for k := range ms.denies.BroaderOrEqualKeys(newKey) { + // Identical deny key needs to be added to merge their entries. + if k != newKey || !newEntry.IsDeny { + return } - return true - }) - if bailed { - return } + } - // Deny takes precedence for the port/proto of the newKey - // for each allow with broader port/proto and narrower ID. - ms.allows.ForEachBroaderKeyWithNarrowerID(newKey, func(k Key, v MapStateEntry) bool { - // If newKey is a superset of the iterated allow key and newKey has - // a less specific port-protocol than the iterated allow key then an - // additional deny entry with port/proto of newKey and with the - // identity of the iterated allow key must be added. - denyKeyCpy := newKey - denyKeyCpy.Identity = k.Identity - l3l4DenyEntry := NewMapStateEntry(newKey, newEntry.DerivedFromRules, 0, "", 0, true, DefaultAuthType, AuthTypeDisabled) - updates = append(updates, MapChange{ - Add: true, - Key: denyKeyCpy, - Value: l3l4DenyEntry, - }) - return true - }) - - // Only a non-wildcard key can have a wildcard superset key - if newKey.Identity != 0 { - ms.allows.ForEachNarrowerKeyWithBroaderID(newKey, func(k Key, v MapStateEntry) bool { - // If this iterated-allow-entry is a wildcard superset of the new-entry - // and it has a more specific port-protocol than the new-entry - // then an additional copy of the new deny entry with the more - // specific port-protocol of the iterated-allow-entry must be inserted. - if k.Identity != 0 { - return true // skip non-wildcard - } - newKeyCpy := k - newKeyCpy.Identity = newKey.Identity - l3l4DenyEntry := NewMapStateEntry(newKey, newEntry.DerivedFromRules, 0, "", 0, true, DefaultAuthType, AuthTypeDisabled) - updates = append(updates, MapChange{ - Add: true, - Key: newKeyCpy, - Value: l3l4DenyEntry, - }) - return true - }) + if newEntry.IsDeny { + // Delete covered allow entries. + for k := range ms.allows.NarrowerOrEqualKeys(newKey) { + ms.deleteKeyWithChanges(k, nil, changes) + } + // Delete covered deny entries, except for identical keys that need to be merged. + for k := range ms.denies.NarrowerKeys(newKey) { + ms.deleteKeyWithChanges(k, nil, changes) } - ms.allows.ForEachNarrowerOrEqualKey(newKey, func(k Key, v MapStateEntry) bool { - // If newKey has a broader (or equal) port-protocol and the newKey's - // identity is a superset (or same) of the iterated identity, then we should - // either delete the iterated-allow-entry (if the identity is the same or - // the newKey is L3 wildcard), or change it to a deny entry otherwise - if newKey.Identity == 0 || newKey.Identity == k.Identity { - deletes = append(deletes, k) - } else { - // When newKey.Identity is not ANY and is different from the subset - // key, we must keep the subset key and make it a deny instead. - // Note that these security identities have no numerical relation to - // each other (e.g, they could be any numbers X and Y) and the - // datapath does an exact match on them. - l3l4DenyEntry := NewMapStateEntry(newKey, newEntry.DerivedFromRules, 0, "", 0, true, DefaultAuthType, AuthTypeDisabled) - updates = append(updates, MapChange{ - Add: true, - Key: k, - Value: l3l4DenyEntry, - }) - } - return true - }) - // Not adding the new L3/L4 deny entries yet so that we do not need to worry about - // them below. - - ms.denies.ForEachNarrowerOrEqualDatapathKey(newKey, func(k Key, v MapStateEntry) bool { - // Identical key needs to remain if owners are different to merge them - if k != newKey || v.DeepEqual(&newEntry) { - // If this iterated-deny-entry is a subset (or equal) of the - // new-entry and the new-entry has a broader (or equal) - // port-protocol the newKey will match all the packets the iterated - // key would, given that there are no more specific or L4-only allow - // entries, and then we can delete the iterated-deny-entry. - deletes = append(deletes, k) + // Add L3/4 deny entry for each more specific allow key with the wildcard identity + // as the more specific allow would otherwise take precedence in the datapath over + // the less specific 'newKey' with a specific identity. + // + // Skip when 'newKey' has no port wildcarding, as then there can't be any narrower + // keys. + if newKey.Identity != 0 && newKey.HasPortWildcard() { + for k := range ms.allows.NarrowerKeysWithWildcardID(newKey) { + ms.insertDependentOfNewKey(newKey, &newEntry, k, changes) } - return true - }) - - for _, key := range deletes { - ms.deleteKeyWithChanges(key, nil, changes) - } - for _, update := range updates { - ms.addKeyWithChanges(update.Key, update.Value, changes) - // L3-only entries can be deleted incrementally so we need to track their - // effects on other entries so that those effects can be reverted when the - // identity is removed. - newEntry.AddDependent(update.Key) } - ms.addKeyWithChanges(newKey, newEntry, changes) } else { + // newEntry is an allow entry. // NOTE: We do not delete redundant allow entries. - var dependents []MapChange - - // Test for bailed case first so that we avoid unnecessary computation if entry is - // not going to be added, or is going to be changed to a deny entry. - bailed := false - insertAsDeny := false - var denyEntry MapStateEntry - ms.denies.ForEachBroaderOrEqualKey(newKey, func(k Key, v MapStateEntry) bool { - // If the iterated-deny-entry is a wildcard or has the same identity then it - // can be bailed out. - if k.Identity == 0 || k.Identity == newKey.Identity { - bailed = true - return false - } - // if any deny key covers this new allow key, then it needs to be inserted - // as deny, if not bailed out. - if !insertAsDeny { - insertAsDeny = true - denyEntry = NewMapStateEntry(k, v.DerivedFromRules, 0, "", 0, true, DefaultAuthType, AuthTypeDisabled) - } else { - // Collect the owners and labels of all the contributing deny rules - denyEntry.Merge(&v) - } - return true - }) - if bailed { - return - } - if insertAsDeny { - ms.authPreferredInsert(newKey, denyEntry, features, changes) - return - } - // Deny takes precedence for the identity of the newKey and the port/proto of the - // iterated narrower port/proto due to broader ID (CIDR or ANY) - ms.denies.ForEachNarrowerKeyWithBroaderID(newKey, func(k Key, v MapStateEntry) bool { - // If the new-entry is a subset of the iterated-deny-entry - // and the new-entry has a less specific port-protocol than the - // iterated-deny-entry then an additional copy of the iterated-deny-entry - // with the identity of the new-entry must be added. - denyKeyCpy := k - denyKeyCpy.Identity = newKey.Identity - l3l4DenyEntry := NewMapStateEntry(k, v.DerivedFromRules, 0, "", 0, true, DefaultAuthType, AuthTypeDisabled) - updates = append(updates, MapChange{ - Add: true, - Key: denyKeyCpy, - Value: l3l4DenyEntry, - }) - // L3-only entries can be deleted incrementally so we need to track their - // effects on other entries so that those effects can be reverted when the - // identity is removed. - dependents = append(dependents, MapChange{ - Key: k, - Value: v, - }) - return true - }) - - if newKey.Identity == 0 { - ms.denies.ForEachBroaderKeyWithNarrowerID(newKey, func(k Key, v MapStateEntry) bool { - // If the new-entry is a wildcard superset of the iterated-deny-entry - // and the new-entry has a more specific port-protocol than the - // iterated-deny-entry then an additional copy of the iterated-deny-entry - // with the more specific port-porotocol of the new-entry must - // be added. - denyKeyCpy := newKey - denyKeyCpy.Identity = k.Identity - l3l4DenyEntry := NewMapStateEntry(k, v.DerivedFromRules, 0, "", 0, true, DefaultAuthType, AuthTypeDisabled) - updates = append(updates, MapChange{ - Add: true, - Key: denyKeyCpy, - Value: l3l4DenyEntry, - }) - // L3-only entries can be deleted incrementally so we need to track their - // effects on other entries so that those effects can be reverted when the - // identity is removed. - dependents = append(dependents, MapChange{ - Key: k, - Value: v, - }) - return true - }) + // Avoid allocs in this block if there are no deny enties + if !ms.denies.Empty() { + // Add L3/4 deny entries for broader deny keys with a specific identity as + // the narrower L4-only allow would otherwise take precedence in the + // datapath. + if newKey.Identity == 0 && newKey.Nexthdr != 0 { // L4-only newKey + for k, v := range ms.denies.BroaderKeysWithSpecificID(newKey) { + ms.insertDependentOfKey(k, v, newKey, changes) + } + } } - for i, update := range updates { - if update.Add { - ms.addKeyWithChanges(update.Key, update.Value, changes) - dep := dependents[i] - ms.addDependentOnEntry(dep.Key, dep.Value, update.Key, changes) - } + // Checking for auth feature here is faster than calling 'authPreferredInsert' and + // checking for it there. + if features.contains(authRules) { + ms.authPreferredInsert(newKey, newEntry, changes) + return } - ms.authPreferredInsert(newKey, newEntry, features, changes) } + + ms.addKeyWithChanges(newKey, newEntry, changes) } -// IsSuperSetOf checks if the receiver Key is a superset of the argument Key, and returns a -// specificity score of the receiver key (higher score is more specific), if so. Being a superset -// means that the receiver key would match all the traffic of the argument key without being the -// same key. Hence, a L3-only key is not a superset of a L4-only key, as the L3-only key would match -// the traffic for the given L3 only, while the L4-only key matches traffic on the given port for -// all the L3's. -// Returns 0 if the receiver key is not a superset of the argument key. -// -// Specificity score for all possible superset wildcard patterns. Datapath requires proto to be specified if port is specified. -// x. L3/proto/port -// 1. */*/* -// 2. */proto/* -// 3. */proto/port -// 4. ID/*/* -// 5. ID/proto/* -// ( ID/proto/port can not be superset of anything ) -func IsSuperSetOf(k, other Key) int { - if k.TrafficDirection() != other.TrafficDirection() { - return 0 // TrafficDirection must match for 'k' to be a superset of 'other' - } - if k.Identity == 0 { - if other.Identity == 0 { - if k.Nexthdr == 0 { // k.DestPort == 0 is implied - if other.Nexthdr != 0 { - return 1 // */*/* is a superset of */proto/x - } // else both are */*/* - } else if k.Nexthdr == other.Nexthdr { - if k.PortIsBroader(other) { - return 2 // */proto/* is a superset of */proto/port - } // else more specific or different ports - } // else more specific or different protocol - } else { - // Wildcard L3 is a superset of a specific L3 only if wildcard L3 is also wildcard L4, or the L4's match between the keys - if k.Nexthdr == 0 { // k.DestPort == 0 is implied - return 1 // */*/* is a superset of ID/x/x - } else if k.Nexthdr == other.Nexthdr { - if k.PortIsBroader(other) { - return 2 // */proto/* is a superset of ID/proto/x - } else if k.PortIsEqual(other) { - return 3 // */proto/port is a superset of ID/proto/port - } // else more specific or different ports - } // else more specific or different protocol - } - } else if k.Identity == other.Identity { - if k.Nexthdr == 0 { - if other.Nexthdr != 0 { - return 4 // ID/*/* is a superset of ID/proto/x - } // else both are ID/*/* - } else if k.Nexthdr == other.Nexthdr { - if k.PortIsBroader(other) { - return 5 // ID/proto/* is a superset of ID/proto/port - } // else more specific or different ports - } // else more specific or different protocol - } // else more specific or different identity - return 0 +// overrideAuthType sets the AuthType of 'v' to that of 'newKey', saving the old entry in 'changes'. +func (ms *mapState) overrideAuthType(newEntry mapStateEntry, k Key, v mapStateEntry, changes ChangeState) { + // Save the old value first + changes.insertOldIfNotExists(k, v) + + // Auth type can be changed in-place, trie is not affected + v.AuthType = newEntry.AuthType + ms.allows.entries[k] = v } // authPreferredInsert applies AuthType of a more generic entry to more specific entries, if not @@ -1249,344 +1162,128 @@ func IsSuperSetOf(k, other Key) int { // This function is expected to be called for a map insertion after deny // entry evaluation. If there is a map entry that is a superset of 'newKey' // which denies traffic matching 'newKey', then this function should not be called. -func (ms *mapState) authPreferredInsert(newKey Key, newEntry MapStateEntry, features policyFeatures, changes ChangeState) { - if features.contains(authRules) { - if newEntry.hasAuthType == DefaultAuthType { - // New entry has a default auth type. - // Fill in the AuthType from more generic entries with an explicit auth type - maxSpecificity := 0 - var l3l4State MapStateMap - - ms.allows.ForEachKeyWithBroaderOrEqualPortProto(newKey, func(k Key, v MapStateEntry) bool { - // Nothing to be done if entry has default AuthType - if v.hasAuthType == DefaultAuthType { - return true - } +func (ms *mapState) authPreferredInsert(newKey Key, newEntry mapStateEntry, changes ChangeState) { + if newEntry.hasAuthType == DefaultAuthType { + // New entry has a default auth type. + + // Fill in the AuthType from the most specific covering key with an explicit + // auth type + for _, v := range ms.allows.CoveringKeys(newKey) { + if v.hasAuthType == ExplicitAuthType { + // AuthType from the most specific covering key is applied to + // 'newEntry' + newEntry.AuthType = v.AuthType + break + } + } - // Find out if 'k' is an identity-port-proto superset of 'newKey' - if specificity := IsSuperSetOf(k, newKey); specificity > 0 { - if specificity > maxSpecificity { - // AuthType from the most specific superset is - // applied to 'newEntry' - newEntry.AuthType = v.AuthType - maxSpecificity = specificity - } - } else { - // Check if a new L3L4 entry must be created due to L3-only - // 'k' specifying an explicit AuthType and an L4-only 'newKey' not - // having an explicit AuthType. In this case AuthType should - // only override the AuthType for the L3 & L4 combination, - // not L4 in general. - // - // These need to be collected and only added if there is a - // superset key of newKey with an explicit auth type. In - // this case AuthType of the new L4-only entry was - // overridden by a more generic entry and 'max_specificity > - // 0' after the loop. - if newKey.Identity == 0 && newKey.Nexthdr != 0 && newKey.DestPort != 0 && - k.Identity != 0 && (k.Nexthdr == 0 || k.Nexthdr == newKey.Nexthdr && k.DestPort == 0) { - newKeyCpy := newKey - newKeyCpy.Identity = k.Identity - l3l4AuthEntry := NewMapStateEntry(k, v.DerivedFromRules, newEntry.ProxyPort, newEntry.Listener, newEntry.priority, false, DefaultAuthType, v.AuthType) - l3l4AuthEntry.DerivedFromRules.MergeSorted(newEntry.DerivedFromRules) - - if l3l4State == nil { - l3l4State = make(MapStateMap) - } - l3l4State[newKeyCpy] = l3l4AuthEntry - } + // Override the AuthType for specific L3/4 keys, if the newKey is L4-only, + // and there is a key with broader port/proto for a specific identity that + // has an explicit auth type. + if newKey.Identity == 0 && newKey.Nexthdr != 0 { // L4-only newKey + for k, v := range ms.allows.BroaderKeysWithSpecificID(newKey) { + if v.hasAuthType == ExplicitAuthType { + ms.insertAuthOverrideFromKey(k, v, newKey, newEntry, changes) } - return true - }) - // Add collected L3/L4 entries if the auth type of the new entry was not - // overridden by a more generic entry. If it was overridden, the new L3L4 - // entries are not needed as the L4-only entry with an overridden AuthType - // will be matched before the L3-only entries in the datapath. - if maxSpecificity == 0 { - for k, v := range l3l4State { - ms.addKeyWithChanges(k, v, changes) - // L3-only entries can be deleted incrementally so we need to track their - // effects on other entries so that those effects can be reverted when the - // identity is removed. - newEntry.AddDependent(k) + } + } + } else { // New entry has an explicit auth type + // Check if the new key is the most specific covering key of any other key + // with the default auth type, and propagate the auth type from the new + // entry to such entries. + if newKey.Identity == 0 { + // A key with a wildcard ID can be the most specific covering key + // for keys with any ID. Hence we need to iterate narrower keys with + // all IDs and: + // - change all iterated keys with a default auth type + // to the auth type of the newKey. + // - stop iteration for any given ID at first key with that ID that + // has an explicit auth type, as that is the most specific covering + // key for the remaining subset keys with that specific ID. + seenIDs := make(IDSet) + for k, v := range ms.allows.SubsetKeys(newKey) { + // Skip if a subset entry has an explicit auth type + if v.hasAuthType == ExplicitAuthType { + // Keep track of IDs for which an explicit auth type + // has been encountered. + seenIDs[k.Identity] = struct{}{} + continue + } + // Override entries for which an explicit auth type has not been + // seen yet. + if _, exists := seenIDs[k.Identity]; !exists { + ms.overrideAuthType(newEntry, k, v, changes) } } } else { - // New entry has an explicit auth type. - // Check if the new entry is the most specific superset of any other entry - // with the default auth type, and propagate the auth type from the new - // entry to such entries. - explicitSubsetKeys := make(Keys) - defaultSubsetKeys := make(map[Key]int) - - ms.allows.ForEachKeyWithNarrowerOrEqualPortProto(newKey, func(k Key, v MapStateEntry) bool { - // Find out if 'newKey' is a superset of 'k' - if specificity := IsSuperSetOf(newKey, k); specificity > 0 { - if v.hasAuthType == ExplicitAuthType { - // store for later comparison - explicitSubsetKeys[k] = struct{}{} - } else { - defaultSubsetKeys[k] = specificity - } - } else if v.hasAuthType == DefaultAuthType { - // Check if a new L3L4 entry must be created due to L3-only - // 'newKey' with an explicit AuthType and an L4-only 'k' not - // having an explicit AuthType. In this case AuthType should - // only override the AuthType for the L3 & L4 combination, - // not L4 in general. - if newKey.Identity != 0 && (newKey.Nexthdr == 0 || newKey.Nexthdr == k.Nexthdr && newKey.DestPort == 0) && - k.Identity == 0 && k.Nexthdr != 0 && k.DestPort != 0 { - newKeyCpy := k - newKeyCpy.Identity = newKey.Identity - l3l4AuthEntry := NewMapStateEntry(newKey, newEntry.DerivedFromRules, v.ProxyPort, v.Listener, v.priority, false, DefaultAuthType, newEntry.AuthType) - l3l4AuthEntry.DerivedFromRules.MergeSorted(v.DerivedFromRules) - ms.addKeyWithChanges(newKeyCpy, l3l4AuthEntry, changes) - // L3-only entries can be deleted incrementally so we need to track their - // effects on other entries so that those effects can be reverted when the - // identity is removed. - newEntry.AddDependent(newKeyCpy) - } + // A key with a specific ID can be the most specific covering key + // only for keys with the same ID. However, a wildcard ID key can also be + // the most specific covering key for those keys, if it has a more + // specific proto/port than the newKey. Hence we need to iterate + // narrower keys with the same or ANY ID and: + // - change all iterated keys with the same ID and a default auth + // type to the auth type of the newKey + // - stop iteration at first key with an explicit auth, as that is + // the most specific covering key for the remaining subset keys with + // the same ID. + for k, v := range ms.allows.SubsetKeys(newKey) { + // Stop if a subset entry has an explicit auth type, as that is more + // specific for all remaining subset keys + if v.hasAuthType == ExplicitAuthType { + break + } + // auth only propagates from a key with specific ID + // to keys with the same ID. + if k.Identity != 0 { + ms.overrideAuthType(newEntry, k, v, changes) } + } - return true - }) - // Find out if this newKey is the most specific superset for all the subset keys with default auth type - Next: - for k, specificity := range defaultSubsetKeys { - for l := range explicitSubsetKeys { - if s := IsSuperSetOf(l, k); s > specificity { - // k has a more specific superset key than the newKey, skip - continue Next + // Override authtype for specific L3L4 keys if 'newKey' with a + // specific ID has an explicit AuthType and an L4-only 'k' has a + // default AuthType. In this case AuthType of 'newEntry' should only + // override the AuthType for the L3 & L4 combination, not L4 in + // general. + // + // Only (partially) wildcarded port can have narrower keys. + if newKey.HasPortWildcard() { + for k, v := range ms.allows.NarrowerKeysWithWildcardID(newKey) { + if v.hasAuthType == DefaultAuthType { + ms.insertAuthOverrideFromNewKey(newKey, &newEntry, k, v, changes) } } - // newKey is the most specific superset with an explicit auth type, - // propagate auth type from newEntry to the entry of k - v, _ := ms.Get(k) - v.AuthType = newEntry.AuthType - ms.addKeyWithChanges(k, v, changes) // Update the map value } } } - ms.addKeyWithChanges(newKey, newEntry, changes) -} -var visibilityDerivedFromLabels = labels.LabelArray{ - labels.NewLabel(LabelKeyPolicyDerivedFrom, LabelVisibilityAnnotation, labels.LabelSourceReserved), + ms.addKeyWithChanges(newKey, newEntry, changes) } -var visibilityDerivedFrom = labels.LabelArrayList{visibilityDerivedFromLabels} - -// insertIfNotExists only inserts `key=value` if `key` does not exist in keys already -// returns 'true' if 'key=entry' was added to 'keys' -func (changes *ChangeState) insertOldIfNotExists(key Key, entry MapStateEntry) bool { - if changes == nil || changes.Old == nil { +// insertIfNotExists only inserts an entry in 'changes.Old' if 'key' does not exist in there already +// and 'key' does not already exist in 'changes.Adds'. This prevents recording "old" values for +// newly added keys. When an entry is updated, we are called before the key is added to +// 'changes.Adds' so we'll record the old value as expected. +// Returns 'true' if an old entry was added. +func (changes *ChangeState) insertOldIfNotExists(key Key, entry mapStateEntry) bool { + if changes == nil || changes.old == nil { return false } - if _, exists := changes.Old[key]; !exists { + if _, exists := changes.old[key]; !exists { // Only insert the old entry if the entry was not first added on this round of // changes. if _, added := changes.Adds[key]; !added { - // new containers to keep this entry separate from the one that may remain in 'keys' - entry.DerivedFromRules = slices.Clone(entry.DerivedFromRules) - entry.owners = maps.Clone(entry.owners) + // Clone to keep this entry separate from the one that may remain in 'keys' + entry.derivedFromRules = slices.Clone(entry.derivedFromRules) + entry.owners = entry.owners.Clone() entry.dependents = maps.Clone(entry.dependents) - changes.Old[key] = entry + changes.old[key] = entry return true } } return false } -// ForEachKeyWithPortProto calls 'f' for each Key and MapStateEntry, where the Key has the same traffic direction and and L4 fields (protocol, destination port and mask). -func (msm *mapStateMap) ForEachKeyWithPortProto(key Key, f func(Key, MapStateEntry) bool) { - // 'Identity' field in 'key' is ignored on by ExactLookup - idSet, ok := msm.trie.ExactLookup(key.PrefixLength(), key) - if ok { - for id := range idSet { - k := key - k.Identity = id - if !msm.forKey(k, f) { - return - } - } - } -} - -// addVisibilityKeys adjusts and expands PolicyMapState keys -// and values to redirect for visibility on the port of the visibility -// annotation while still denying traffic on this port for identities -// for which the traffic is denied. -// -// Datapath lookup order is, from highest to lowest precedence: -// 1. L3/L4 -// 2. L4-only (wildcard L3) -// 3. L3-only (wildcard L4) -// 4. Allow-all -// -// This means that the L4-only allow visibility key can only be added if there is an -// allow-all key, and all L3-only deny keys are expanded to L3/L4 keys. If no -// L4-only key is added then also the L3-only allow keys need to be expanded to -// L3/L4 keys for visibility redirection. In addition the existing L3/L4 and L4-only -// allow keys need to be redirected to the proxy port, if not already redirected. -// -// The above can be accomplished by: -// -// 1. Change existing L4-only ALLOW key on matching port that does not already -// redirect to redirect. -// - e.g., 0:80=allow,0 -> 0:80=allow, -// 2. If allow-all policy exists, add L4-only visibility redirect key if the L4-only -// key does not already exist. -// - e.g., 0:0=allow,0 -> add 0:80=allow, if 0:80 does not exist -// - this allows all traffic on port 80, but see step 5 below. -// 3. Change all L3/L4 ALLOW keys on matching port that do not already redirect to -// redirect. -// - e.g, :80=allow,0 -> :80=allow, -// 4. For each L3-only ALLOW key add the corresponding L3/L4 ALLOW redirect if no -// L3/L4 key already exists and no L4-only key already exists and one is not added. -// - e.g., :0=allow,0 -> add :80=allow, if :80 -// and 0:80 do not exist -// 5. If a new L4-only key was added: For each L3-only DENY key add the -// corresponding L3/L4 DENY key if no L3/L4 key already exists. -// - e.g., :0=deny,0 -> add :80=deny,0 if :80 does not exist -// -// With the above we only change/expand existing allow keys to redirect, and -// expand existing drop keys to also drop on the port of interest, if a new -// L4-only key allowing the port is added. -// -// 'adds' and 'oldValues' are updated with the changes made. 'adds' contains both the added and -// changed keys. 'oldValues' contains the old values for changed keys. This function does not -// delete any keys. -func (ms *mapState) addVisibilityKeys(e PolicyOwner, redirectPort uint16, visMeta *VisibilityMetadata, changes ChangeState) { - direction := trafficdirection.Egress - if visMeta.Ingress { - direction = trafficdirection.Ingress - } - - key := KeyForDirection(direction).WithPortProto(visMeta.Proto, visMeta.Port) - entry := NewMapStateEntry(nil, visibilityDerivedFrom, redirectPort, "", 0, false, DefaultAuthType, AuthTypeDisabled) - - _, haveAllowAllKey := ms.Get(allKey[direction]) - l4Only, haveL4OnlyKey := ms.Get(key) - addL4OnlyKey := false - if haveL4OnlyKey && !l4Only.IsDeny && l4Only.ProxyPort == 0 { - // 1. Change existing L4-only ALLOW key on matching port that does not already - // redirect to redirect. - e.PolicyDebug(logrus.Fields{ - logfields.BPFMapKey: key, - logfields.BPFMapValue: entry, - }, "addVisibilityKeys: Changing L4-only ALLOW key for visibility redirect") - ms.addKeyWithChanges(key, entry, changes) - } - if haveAllowAllKey && !haveL4OnlyKey { - // 2. If allow-all policy exists, add L4-only visibility redirect key if the L4-only - // key does not already exist. - e.PolicyDebug(logrus.Fields{ - logfields.BPFMapKey: key, - logfields.BPFMapValue: entry, - }, "addVisibilityKeys: Adding L4-only ALLOW key for visibility redirect") - addL4OnlyKey = true - ms.addKeyWithChanges(key, entry, changes) - } - // We need to make changes to the map - // outside of iteration. - var updates []MapChange - // - // Loop through all L3 keys in the traffic direction of the new key - // - - // Find entries with the same L4 - ms.allows.ForEachKeyWithPortProto(key, func(k Key, v MapStateEntry) bool { - if k.Identity != 0 { - if v.ProxyPort == 0 { - // 3. Change all L3/L4 ALLOW keys on matching port that do not - // already redirect to redirect. - v.ProxyPort = redirectPort - // redirect port is used as the default priority for tie-breaking - // purposes when two different selectors have conflicting - // redirects. Explicit listener references in the policy can specify - // a priority, but only the default is used for visibility policy, - // as visibility will be achieved by any of the redirects. - v.priority = redirectPort - v.Listener = "" - v.DerivedFromRules = visibilityDerivedFrom - e.PolicyDebug(logrus.Fields{ - logfields.BPFMapKey: k, - logfields.BPFMapValue: v, - }, "addVisibilityKeys: Changing L3/L4 ALLOW key for visibility redirect") - updates = append(updates, MapChange{ - Add: true, - Key: k, - Value: v, - }) - } - } - return true - }) - - // Find Wildcarded L4 allows, i.e., L3-only entries - if !haveL4OnlyKey && !addL4OnlyKey { - ms.allows.ForEachKeyWithPortProto(allKey[key.TrafficDirection()], func(k Key, v MapStateEntry) bool { - if k.Identity != 0 { - k2 := key - k2.Identity = k.Identity - // 4. For each L3-only ALLOW key add the corresponding L3/L4 - // ALLOW redirect if no L3/L4 key already exists and no - // L4-only key already exists and one is not added. - if _, ok := ms.Get(k2); !ok { - d2 := labels.LabelArrayList{visibilityDerivedFromLabels} - d2.MergeSorted(v.DerivedFromRules) - v2 := NewMapStateEntry(k, d2, redirectPort, "", 0, false, v.hasAuthType, v.AuthType) - e.PolicyDebug(logrus.Fields{ - logfields.BPFMapKey: k2, - logfields.BPFMapValue: v2, - }, "addVisibilityKeys: Extending L3-only ALLOW key to L3/L4 key for visibility redirect") - updates = append(updates, MapChange{ - Add: true, - Key: k2, - Value: v2, - }) - // Mark the new entry as a dependent of 'v' - ms.addDependentOnEntry(k, v, k2, changes) - } - } - return true - }) - } - - // Find Wildcarded L4 denies, i.e., L3-only entries - if addL4OnlyKey { - ms.denies.ForEachKeyWithPortProto(allKey[key.TrafficDirection()], func(k Key, v MapStateEntry) bool { - if k.Identity != 0 { - k2 := key - k2.Identity = k.Identity - // 5. If a new L4-only key was added: For each L3-only DENY - // key add the corresponding L3/L4 DENY key if no L3/L4 - // key already exists. - if _, ok := ms.Get(k2); !ok { - v2 := NewMapStateEntry(k, v.DerivedFromRules, 0, "", 0, true, DefaultAuthType, AuthTypeDisabled) - e.PolicyDebug(logrus.Fields{ - logfields.BPFMapKey: k2, - logfields.BPFMapValue: v2, - }, "addVisibilityKeys: Extending L3-only DENY key to L3/L4 key to deny a port with visibility annotation") - updates = append(updates, MapChange{ - Add: true, - Key: k2, - Value: v2, - }) - // Mark the new entry as a dependent of 'v' - ms.addDependentOnEntry(k, v, k2, changes) - } - } - return true - }) - } - - for _, update := range updates { - ms.addKeyWithChanges(update.Key, update.Value, changes) - } -} - // determineAllowLocalhostIngress determines whether communication should be allowed // from the localhost. It inserts the Key corresponding to the localhost in // the desiredPolicyKeys if the localhost is allowed to communicate with the @@ -1598,8 +1295,8 @@ func (ms *mapState) determineAllowLocalhostIngress() { labels.NewLabel(LabelKeyPolicyDerivedFrom, LabelAllowLocalHostIngress, labels.LabelSourceReserved), }, } - es := NewMapStateEntry(nil, derivedFrom, 0, "", 0, false, ExplicitAuthType, AuthTypeDisabled) // Authentication never required for local host ingress - ms.denyPreferredInsert(localHostKey, es, allFeatures) + entry := newMapStateEntry(nil, derivedFrom, 0, 0, false, ExplicitAuthType, AuthTypeDisabled) // Authentication never required for local host ingress + ms.insertWithChanges(localHostKey, entry, allFeatures, ChangeState{}) } } @@ -1609,98 +1306,11 @@ func (ms *mapState) determineAllowLocalhostIngress() { // Note that this is used when policy is not enforced, so authentication is explicitly not required. func (ms *mapState) allowAllIdentities(ingress, egress bool) { if ingress { - derivedFrom := labels.LabelArrayList{ - labels.LabelArray{ - labels.NewLabel(LabelKeyPolicyDerivedFrom, LabelAllowAnyIngress, labels.LabelSourceReserved), - }, - } - ms.allows.upsert(allKey[trafficdirection.Ingress], NewMapStateEntry(nil, derivedFrom, 0, "", 0, false, ExplicitAuthType, AuthTypeDisabled)) + ms.allows.upsert(allKey[trafficdirection.Ingress], newMapStateEntry(nil, LabelsAllowAnyIngress, 0, 0, false, ExplicitAuthType, AuthTypeDisabled)) } if egress { - derivedFrom := labels.LabelArrayList{ - labels.LabelArray{ - labels.NewLabel(LabelKeyPolicyDerivedFrom, LabelAllowAnyEgress, labels.LabelSourceReserved), - }, - } - ms.allows.upsert(allKey[trafficdirection.Egress], NewMapStateEntry(nil, derivedFrom, 0, "", 0, false, ExplicitAuthType, AuthTypeDisabled)) - } -} - -func (ms *mapState) deniesL4(policyOwner PolicyOwner, l4 *L4Filter) bool { - port := uint16(l4.Port) - proto := l4.U8Proto - - // resolve named port - if port == 0 && l4.PortName != "" { - port = policyOwner.GetNamedPort(l4.Ingress, l4.PortName, proto) - if port == 0 { - return true - } - } - - var key Key - if l4.Ingress { - key = allKey[trafficdirection.Ingress] - } else { - key = allKey[trafficdirection.Egress] + ms.allows.upsert(allKey[trafficdirection.Egress], newMapStateEntry(nil, LabelsAllowAnyEgress, 0, 0, false, ExplicitAuthType, AuthTypeDisabled)) } - - // Are we explicitly denying all traffic? - v, ok := ms.Get(key) - if ok && v.IsDeny { - return true - } - - // Are we explicitly denying this L4-only traffic? - key.DestPort = port - key.Nexthdr = proto - v, ok = ms.Get(key) - if ok && v.IsDeny { - return true - } - - // The given L4 is not categorically denied. - // Traffic to/from a specific L3 on any of the selectors can still be denied. - return false -} - -func (ms *mapState) GetIdentities(log *logrus.Logger) (ingIdentities, egIdentities []int64) { - return ms.getIdentities(log, false) -} - -func (ms *mapState) GetDenyIdentities(log *logrus.Logger) (ingIdentities, egIdentities []int64) { - return ms.getIdentities(log, true) -} - -// GetIdentities returns the ingress and egress identities stored in the -// MapState. -// Used only for API requests. -func (ms *mapState) getIdentities(log *logrus.Logger, denied bool) (ingIdentities, egIdentities []int64) { - ms.ForEach(func(key Key, entry MapStateEntry) bool { - if denied != entry.IsDeny { - return true - } - if key.DestPort != 0 { - // If the port is non-zero, then the Key no longer only applies - // at L3. AllowedIngressIdentities and AllowedEgressIdentities - // contain sets of which identities (i.e., label-based L3 only) - // are allowed, so anything which contains L4-related policy should - // not be added to these sets. - return true - } - switch key.TrafficDirection() { - case trafficdirection.Ingress: - ingIdentities = append(ingIdentities, int64(key.Identity)) - case trafficdirection.Egress: - egIdentities = append(egIdentities, int64(key.Identity)) - default: - td := key.TrafficDirection() - log.WithField(logfields.TrafficDirection, td). - Errorf("Unexpected traffic direction present in policy map state for endpoint") - } - return true - }) - return ingIdentities, egIdentities } // MapChanges collects updates to the endpoint policy on the @@ -1709,11 +1319,17 @@ func (ms *mapState) getIdentities(log *logrus.Logger, denied bool) (ingIdentitie type MapChanges struct { firstVersion versioned.KeepVersion mutex lock.Mutex - changes []MapChange - synced []MapChange + changes []mapChange + synced []mapChange version *versioned.VersionHandle } +type mapChange struct { + Add bool // false deletes + Key Key + Value mapStateEntry +} + type MapChange struct { Add bool // false deletes Key Key @@ -1725,19 +1341,27 @@ type MapChange struct { // // The caller is responsible for making sure the same identity is not // present in both 'adds' and 'deletes'. -func (mc *MapChanges) AccumulateMapChanges(cs CachedSelector, adds, deletes []identity.NumericIdentity, keys []Key, value MapStateEntry) { +func (mc *MapChanges) AccumulateMapChanges(adds, deletes []identity.NumericIdentity, keys []Key, value mapStateEntry) { mc.mutex.Lock() defer mc.mutex.Unlock() for _, id := range adds { for _, k := range keys { k.Identity = id - mc.changes = append(mc.changes, MapChange{Add: true, Key: k, Value: value}) + mc.changes = append(mc.changes, mapChange{ + Add: true, + Key: k, + Value: value, + }) } } for _, id := range deletes { for _, k := range keys { k.Identity = id - mc.changes = append(mc.changes, MapChange{Add: false, Key: k, Value: value}) + mc.changes = append(mc.changes, mapChange{ + Add: false, + Key: k, + Value: value, + }) } } } @@ -1780,42 +1404,22 @@ func (mc *MapChanges) consumeMapChanges(p *EndpointPolicy, features policyFeatur changes := ChangeState{ Adds: make(Keys, len(mc.synced)), Deletes: make(Keys, len(mc.synced)), - Old: make(map[Key]MapStateEntry, len(mc.synced)), - } - - var redirects map[string]uint16 - if p.PolicyOwner != nil { - redirects = p.PolicyOwner.GetRealizedRedirects() + old: make(map[Key]mapStateEntry, len(mc.synced)), } for i := range mc.synced { - if mc.synced[i].Add { - // Redirect entries for unrealized redirects come in with an invalid - // redirect port (65535), replace it with the actual proxy port number. - key := mc.synced[i].Key - entry := mc.synced[i].Value - if entry.ProxyPort == unrealizedRedirectPort { - var exists bool - proxyID := ProxyIDFromKey(uint16(p.PolicyOwner.GetID()), key, entry.Listener) - entry.ProxyPort, exists = redirects[proxyID] - if !exists { - log.WithFields(logrus.Fields{ - logfields.PolicyKey: key, - logfields.PolicyEntry: entry, - }).Warn("consumeMapChanges: Skipping entry for unrealized redirect") - continue - } - } + key := mc.synced[i].Key + entry := mc.synced[i].Value - // insert but do not allow non-redirect entries to overwrite a redirect entry, - // nor allow non-deny entries to overwrite deny entries. - // Collect the incremental changes to the overall state in 'mc.adds' and 'mc.deletes'. - p.policyMapState.denyPreferredInsertWithChanges(key, entry, features, changes) + if mc.synced[i].Add { + // Insert the key to and collect the incremental changes to the overall + // state in 'changes' + p.policyMapState.insertWithChanges(key, entry, features, changes) } else { - // Delete the contribution of this cs to the key and collect incremental changes - for cs := range mc.synced[i].Value.owners { // get the sole selector - p.policyMapState.deleteKeyWithChanges(mc.synced[i].Key, cs, changes) - } + // Delete the contribution of this cs to the key and collect incremental + // changes + cs, _ := entry.owners.Get() // get the sole selector + p.policyMapState.deleteKeyWithChanges(key, cs, changes) } } diff --git a/vendor/github.com/cilium/cilium/pkg/policy/repository.go b/vendor/github.com/cilium/cilium/pkg/policy/repository.go index a1e3aac373..11018fd7cb 100644 --- a/vendor/github.com/cilium/cilium/pkg/policy/repository.go +++ b/vendor/github.com/cilium/cilium/pkg/policy/repository.go @@ -13,6 +13,7 @@ import ( "sync/atomic" cilium "github.com/cilium/proxy/go/cilium/api" + "k8s.io/apimachinery/pkg/util/sets" "github.com/cilium/cilium/api/v1/models" "github.com/cilium/cilium/pkg/crypto/certificatemanager" @@ -106,14 +107,46 @@ func (p *policyContext) SetDeny(deny bool) bool { return oldDeny } +// RepositoryLock exposes methods to protect the whole policy tree. +type RepositoryLock interface { + Lock() + Unlock() + RLock() + RUnlock() +} + +type PolicyRepository interface { + RepositoryLock + + AddListLocked(rules api.Rules) (ruleSlice, uint64) + BumpRevision() uint64 + DeleteByLabelsLocked(lbls labels.LabelArray) (ruleSlice, uint64, int) + DeleteByResourceLocked(rid ipcachetypes.ResourceID) (ruleSlice, uint64) + GetAuthTypes(localID identity.NumericIdentity, remoteID identity.NumericIdentity) AuthTypes + GetEnvoyHTTPRules(l7Rules *api.L7Rules, ns string) (*cilium.HttpNetworkPolicyRules, bool) + GetPolicyCache() *PolicyCache + GetRevision() uint64 + GetRulesList() *models.Policy + GetSelectorCache() *SelectorCache + GetRepositoryChangeQueue() *eventqueue.EventQueue + GetRuleReactionQueue() *eventqueue.EventQueue + Iterate(f func(rule *api.Rule)) + Release(rs ruleSlice) + ReplaceByResourceLocked(rules api.Rules, resource ipcachetypes.ResourceID) (newRules ruleSlice, oldRules ruleSlice, revision uint64) + SearchRLocked(lbls labels.LabelArray) api.Rules + SetEnvoyRulesFunc(f func(certificatemanager.SecretManager, *api.L7Rules, string) (*cilium.HttpNetworkPolicyRules, bool)) + Start() +} + // Repository is a list of policy rules which in combination form the security // policy. A policy repository can be type Repository struct { - // Mutex protects the whole policy tree - Mutex lock.RWMutex + // mutex protects the whole policy tree + mutex lock.RWMutex - rules map[ruleKey]*rule - rulesByResource map[ipcachetypes.ResourceID]map[ruleKey]*rule + rules map[ruleKey]*rule + rulesByNamespace map[string]sets.Set[ruleKey] + rulesByResource map[ipcachetypes.ResourceID]map[ruleKey]*rule // We will need a way to synthesize a rule key for rules without a resource; // these are - in practice - very rare, as they only come from the local API, @@ -125,15 +158,15 @@ type Repository struct { // Always positive (>0). revision atomic.Uint64 - // RepositoryChangeQueue is a queue which serializes changes to the policy + // repositoryChangeQueue is a queue which serializes changes to the policy // repository. - RepositoryChangeQueue *eventqueue.EventQueue + repositoryChangeQueue *eventqueue.EventQueue - // RuleReactionQueue is a queue which serializes the resultant events that + // ruleReactionQueue is a queue which serializes the resultant events that // need to occur after updating the state of the policy repository. This // can include queueing endpoint regenerations, policy revision increments // for endpoints, etc. - RuleReactionQueue *eventqueue.EventQueue + ruleReactionQueue *eventqueue.EventQueue // SelectorCache tracks the selectors used in the policies // resolved from the repository. @@ -148,11 +181,39 @@ type Repository struct { getEnvoyHTTPRules func(certificatemanager.SecretManager, *api.L7Rules, string) (*cilium.HttpNetworkPolicyRules, bool) } +// Lock acquiers the lock of the whole policy tree. +func (p *Repository) Lock() { + p.mutex.Lock() +} + +// Unlock releases the lock of the whole policy tree. +func (p *Repository) Unlock() { + p.mutex.Unlock() +} + +// RLock acquiers the read lock of the whole policy tree. +func (p *Repository) RLock() { + p.mutex.RLock() +} + +// RUnlock releases the read lock of the whole policy tree. +func (p *Repository) RUnlock() { + p.mutex.RUnlock() +} + // GetSelectorCache() returns the selector cache used by the Repository func (p *Repository) GetSelectorCache() *SelectorCache { return p.selectorCache } +func (p *Repository) GetRepositoryChangeQueue() *eventqueue.EventQueue { + return p.repositoryChangeQueue +} + +func (p *Repository) GetRuleReactionQueue() *eventqueue.EventQueue { + return p.ruleReactionQueue +} + // GetAuthTypes returns the AuthTypes required by the policy between the localID and remoteID func (p *Repository) GetAuthTypes(localID, remoteID identity.NumericIdentity) AuthTypes { return p.policyCache.GetAuthTypes(localID, remoteID) @@ -180,7 +241,7 @@ func NewPolicyRepository( initialIDs identity.IdentityMap, certManager certificatemanager.CertificateManager, secretManager certificatemanager.SecretManager, - idmgr *identitymanager.IdentityManager, + idmgr identitymanager.IDManager, ) *Repository { repo := NewStoppedPolicyRepository(initialIDs, certManager, secretManager, idmgr) repo.Start() @@ -196,15 +257,16 @@ func NewStoppedPolicyRepository( initialIDs identity.IdentityMap, certManager certificatemanager.CertificateManager, secretManager certificatemanager.SecretManager, - idmgr *identitymanager.IdentityManager, + idmgr identitymanager.IDManager, ) *Repository { selectorCache := NewSelectorCache(initialIDs) repo := &Repository{ - rules: make(map[ruleKey]*rule), - rulesByResource: make(map[ipcachetypes.ResourceID]map[ruleKey]*rule), - selectorCache: selectorCache, - certManager: certManager, - secretManager: secretManager, + rules: make(map[ruleKey]*rule), + rulesByNamespace: make(map[string]sets.Set[ruleKey]), + rulesByResource: make(map[ipcachetypes.ResourceID]map[ruleKey]*rule), + selectorCache: selectorCache, + certManager: certManager, + secretManager: secretManager, } repo.revision.Store(1) repo.policyCache = NewPolicyCache(repo, idmgr) @@ -253,10 +315,10 @@ func (state *traceState) trace(rules int, ctx *SearchContext) { // // Must only be called if using [NewStoppedPolicyRepository] func (p *Repository) Start() { - p.RepositoryChangeQueue = eventqueue.NewEventQueueBuffered("repository-change-queue", option.Config.PolicyQueueSize) - p.RuleReactionQueue = eventqueue.NewEventQueueBuffered("repository-reaction-queue", option.Config.PolicyQueueSize) - p.RepositoryChangeQueue.Run() - p.RuleReactionQueue.Run() + p.repositoryChangeQueue = eventqueue.NewEventQueueBuffered("repository-change-queue", option.Config.PolicyQueueSize) + p.ruleReactionQueue = eventqueue.NewEventQueueBuffered("repository-reaction-queue", option.Config.PolicyQueueSize) + p.repositoryChangeQueue.Run() + p.ruleReactionQueue.Run() } // ResolveL4IngressPolicy resolves the L4 ingress policy for a set of endpoints @@ -451,6 +513,10 @@ func (p *Repository) ReplaceByResourceLocked(rules api.Rules, resource ipcachety func (p *Repository) insert(r *rule) { p.rules[r.key] = r + if _, ok := p.rulesByNamespace[r.key.resource.Namespace()]; !ok { + p.rulesByNamespace[r.key.resource.Namespace()] = sets.New[ruleKey]() + } + p.rulesByNamespace[r.key.resource.Namespace()].Insert(r.key) rid := r.key.resource if len(rid) > 0 { if p.rulesByResource[rid] == nil { @@ -467,6 +533,10 @@ func (p *Repository) del(key ruleKey) { return } delete(p.rules, key) + p.rulesByNamespace[key.resource.Namespace()].Delete(key) + if len(p.rulesByNamespace[key.resource.Namespace()]) == 0 { + delete(p.rulesByNamespace, key.resource.Namespace()) + } rid := key.resource if len(rid) > 0 && p.rulesByResource[rid] != nil { @@ -509,16 +579,16 @@ func (p *Repository) MustAddList(rules api.Rules) (ruleSlice, uint64) { panic(err) } } - p.Mutex.Lock() - defer p.Mutex.Unlock() + p.mutex.Lock() + defer p.mutex.Unlock() return p.AddListLocked(rules) } // Iterate iterates the policy repository, calling f for each rule. It is safe // to execute Iterate concurrently. func (p *Repository) Iterate(f func(rule *api.Rule)) { - p.Mutex.RWMutex.Lock() - defer p.Mutex.RWMutex.Unlock() + p.mutex.RWMutex.Lock() + defer p.mutex.RWMutex.Unlock() for _, r := range p.rules { f(&r.Rule) } @@ -584,8 +654,8 @@ func (p *Repository) DeleteByResourceLocked(rid ipcachetypes.ResourceID) (ruleSl // DeleteByLabels deletes all rules in the policy repository which contain the // specified labels func (p *Repository) DeleteByLabels(lbls labels.LabelArray) (uint64, int) { - p.Mutex.Lock() - defer p.Mutex.Unlock() + p.mutex.Lock() + defer p.mutex.Unlock() _, rev, numDeleted := p.DeleteByLabelsLocked(lbls) return rev, numDeleted } @@ -600,24 +670,10 @@ func JSONMarshalRules(rules api.Rules) string { return string(b) } -// GetJSON returns all rules of the policy repository as string in JSON -// representation -func (p *Repository) GetJSON() string { - p.Mutex.RLock() - defer p.Mutex.RUnlock() - - result := api.Rules{} - for _, r := range p.rules { - result = append(result, &r.Rule) - } - - return JSONMarshalRules(result) -} - // GetRulesMatching returns whether any of the rules in a repository contain a // rule with labels matching the labels in the provided LabelArray. // -// Must be called with p.Mutex held +// Must be called with p.mutex held func (p *Repository) GetRulesMatching(lbls labels.LabelArray) (ingressMatch bool, egressMatch bool) { ingressMatch = false egressMatch = false @@ -645,27 +701,11 @@ func (p *Repository) GetRulesMatching(lbls labels.LabelArray) (ingressMatch bool return } -// NumRules returns the amount of rules in the policy repository. -// -// Must be called with p.Mutex held -func (p *Repository) NumRules() int { - return len(p.rules) -} - // GetRevision returns the revision of the policy repository func (p *Repository) GetRevision() uint64 { return p.revision.Load() } -// Empty returns 'true' if repository has no rules, 'false' otherwise. -// -// Must be called without p.Mutex held -func (p *Repository) Empty() bool { - p.Mutex.Lock() - defer p.Mutex.Unlock() - return p.NumRules() == 0 -} - // BumpRevision allows forcing policy regeneration func (p *Repository) BumpRevision() uint64 { metrics.PolicyRevision.Inc() @@ -674,8 +714,8 @@ func (p *Repository) BumpRevision() uint64 { // GetRulesList returns the current policy func (p *Repository) GetRulesList() *models.Policy { - p.Mutex.RLock() - defer p.Mutex.RUnlock() + p.mutex.RLock() + defer p.mutex.RUnlock() lbls := labels.ParseSelectLabelArrayFromArray([]string{}) ruleList := p.SearchRLocked(lbls) @@ -777,11 +817,23 @@ func (p *Repository) computePolicyEnforcementAndRules(securityIdentity *identity } matchingRules = []*rule{} - for _, r := range p.rules { + // Match cluster-wide rules + for rKey := range p.rulesByNamespace[""] { + r := p.rules[rKey] if r.matchesSubject(securityIdentity) { matchingRules = append(matchingRules, r) } } + // Match namespace-specific rules + namespace := lbls.Get(labels.LabelSourceK8sKeyPrefix + k8sConst.PodNamespaceLabel) + if namespace != "" { + for rKey := range p.rulesByNamespace[namespace] { + r := p.rules[rKey] + if r.matchesSubject(securityIdentity) { + matchingRules = append(matchingRules, r) + } + } + } // If policy enforcement is enabled for the daemon, then it has to be // enabled for the endpoint. diff --git a/vendor/github.com/cilium/cilium/pkg/policy/resolve.go b/vendor/github.com/cilium/cilium/pkg/policy/resolve.go index 5a357534f8..75b15ec6cc 100644 --- a/vendor/github.com/cilium/cilium/pkg/policy/resolve.go +++ b/vendor/github.com/cilium/cilium/pkg/policy/resolve.go @@ -4,9 +4,14 @@ package policy import ( + "errors" + "fmt" + "iter" + "github.com/sirupsen/logrus" "github.com/cilium/cilium/pkg/container/versioned" + "github.com/cilium/cilium/pkg/labels" "github.com/cilium/cilium/pkg/logging/logfields" "github.com/cilium/cilium/pkg/u8proto" ) @@ -59,21 +64,35 @@ type EndpointPolicy struct { // Proxy port 0 indicates no proxy redirection. // All fields within the Key and the proxy port must be in host byte-order. // Must only be accessed with PolicyOwner (aka Endpoint) lock taken. - policyMapState MapState + policyMapState *mapState // policyMapChanges collects pending changes to the PolicyMapState policyMapChanges MapChanges // PolicyOwner describes any type which consumes this EndpointPolicy object. PolicyOwner PolicyOwner + + // Redirects contains the proxy ports needed for this EndpointPolicy. + // If any redirects are missing a new policy will be computed to rectify it, so this is + // constant for the lifetime of this EndpointPolicy. + Redirects map[string]uint16 +} + +// LookupRedirectPort returns the redirect L4 proxy port for the given input parameters. +// Returns 0 if not found or the filter doesn't require a redirect. +// Returns an error if the redirect port can not be found. +// This is called when accumulating incremental map changes, endpoint lock must not be taken. +func (p *EndpointPolicy) LookupRedirectPort(ingress bool, protocol string, port uint16, listener string) (uint16, error) { + proxyID := ProxyID(uint16(p.PolicyOwner.GetID()), ingress, protocol, port, listener) + if proxyPort, exists := p.Redirects[proxyID]; exists { + return proxyPort, nil + } + return 0, fmt.Errorf("Proxy port for redirect %q not found", proxyID) } // PolicyOwner is anything which consumes a EndpointPolicy. type PolicyOwner interface { GetID() uint64 - LookupRedirectPort(ingress bool, protocol string, port uint16, listener string) (uint16, error) - GetRealizedRedirects() map[string]uint16 - HasBPFPolicyMap() bool GetNamedPort(ingress bool, name string, proto u8proto.U8proto) uint16 PolicyDebug(fields logrus.Fields, msg string) } @@ -113,7 +132,7 @@ func (p *selectorPolicy) Detach() { // Called without holding the Selector cache or Repository locks. // PolicyOwner (aka Endpoint) is also unlocked during this call, // but the Endpoint's build mutex is held. -func (p *selectorPolicy) DistillPolicy(policyOwner PolicyOwner, isHost bool) *EndpointPolicy { +func (p *selectorPolicy) DistillPolicy(policyOwner PolicyOwner, redirects map[string]uint16, isHost bool) *EndpointPolicy { var calculatedPolicy *EndpointPolicy // EndpointPolicy is initialized while 'GetCurrentVersionHandleFunc' keeps the selector @@ -132,14 +151,15 @@ func (p *selectorPolicy) DistillPolicy(policyOwner PolicyOwner, isHost bool) *En calculatedPolicy = &EndpointPolicy{ selectorPolicy: p, VersionHandle: version, - policyMapState: NewMapState(), + policyMapState: newMapState(), policyMapChanges: MapChanges{ firstVersion: version.Version(), }, PolicyOwner: policyOwner, + Redirects: redirects, } // Register the new EndpointPolicy as a receiver of incremental - // updates before selector cache lock is released by 'GetHandle'. + // updates before selector cache lock is released by 'GetCurrentVersionHandleFunc'. p.insertUser(calculatedPolicy) }) @@ -168,23 +188,6 @@ func (p *EndpointPolicy) Ready() (err error) { return err } -// GetPolicyMap gets the policy map state as the interface -// MapState -func (p *EndpointPolicy) GetPolicyMap() MapState { - return p.policyMapState -} - -// SetPolicyMap sets the policy map state as the interface -// MapState. If the main argument is nil, then this method -// will initialize a new MapState object for the caller. -func (p *EndpointPolicy) SetPolicyMap(ms MapState) { - if ms == nil { - p.policyMapState = NewMapState() - return - } - p.policyMapState = ms -} - // Detach removes EndpointPolicy references from selectorPolicy // to allow the EndpointPolicy to be GC'd. // PolicyOwner (aka Endpoint) is also locked during this call. @@ -201,35 +204,115 @@ func (p *EndpointPolicy) Detach() { p.policyMapChanges.detach() } -// NewMapStateWithInsert returns a new MapState and an insert function that can be used to populate -// it. We keep general insert functions private so that the caller can only insert to this specific -// map. -func NewMapStateWithInsert() (MapState, func(k Key, e MapStateEntry)) { - currentMap := NewMapState() +func (p *EndpointPolicy) Len() int { + return p.policyMapState.Len() +} - return currentMap, func(k Key, e MapStateEntry) { - currentMap.insert(k, e) +func (p *EndpointPolicy) Get(key Key) (MapStateEntry, bool) { + return p.policyMapState.Get(key) +} + +var errMissingKey = errors.New("Key not found") + +// GetRuleLabels returns the list of labels of the rules that contributed +// to the entry at this key. +// The returned LabelArrayList is shallow-copied and therefore must not be mutated. +func (p *EndpointPolicy) GetRuleLabels(k Key) (labels.LabelArrayList, error) { + entry, ok := p.policyMapState.get(k) + if !ok { + return nil, errMissingKey } + return entry.GetRuleLabels(), nil } -func (p *EndpointPolicy) InsertMapState(key Key, entry MapStateEntry) { - // SelectorCache used as Identities interface which only has GetPrefix() that needs no lock - p.policyMapState.insert(key, entry) +func (p *EndpointPolicy) Entries() iter.Seq2[Key, MapStateEntry] { + return func(yield func(Key, MapStateEntry) bool) { + p.policyMapState.ForEach(yield) + } } -func (p *EndpointPolicy) DeleteMapState(key Key) { - // SelectorCache used as Identities interface which only has GetPrefix() that needs no lock - p.policyMapState.delete(key) +func (p *EndpointPolicy) Equals(other MapStateMap) bool { + return p.policyMapState.Equals(other) } -func (p *EndpointPolicy) RevertChanges(changes ChangeState) { - // SelectorCache used as Identities interface which only has GetPrefix() that needs no lock - p.policyMapState.revertChanges(changes) +func (p *EndpointPolicy) Diff(expected MapStateMap) string { + return p.policyMapState.Diff(expected) +} + +func (p *EndpointPolicy) Empty() bool { + return p.policyMapState.Empty() +} + +// Updated returns an iterator for all key/entry pairs in 'p' that are either new or updated +// compared to the entries in 'realized'. +// Here 'realized' is another EndpointPolicy. +// This can be used to figure out which entries need to be added to or updated in 'realised'. +func (p *EndpointPolicy) Updated(realized *EndpointPolicy) iter.Seq2[Key, MapStateEntry] { + return func(yield func(Key, MapStateEntry) bool) { + p.policyMapState.ForEach(func(key Key, entry MapStateEntry) bool { + if oldEntry, ok := realized.policyMapState.Get(key); !ok || oldEntry != entry { + if !yield(key, entry) { + return false + } + } + return true + }) + } +} + +// Missing returns an iterator for all key/entry pairs in 'realized' that missing from 'p'. +// Here 'realized' is another EndpointPolicy. +// This can be used to figure out which entries in 'realised' need to be deleted. +func (p *EndpointPolicy) Missing(realized *EndpointPolicy) iter.Seq2[Key, MapStateEntry] { + return func(yield func(Key, MapStateEntry) bool) { + realized.policyMapState.ForEach(func(key Key, entry MapStateEntry) bool { + // If key that is in realized state is not in desired state, just remove it. + if _, ok := p.policyMapState.Get(key); !ok { + if !yield(key, entry) { + return false + } + } + return true + }) + } +} + +// UpdatedMap returns an iterator for all key/entry pairs in 'p' that are either new or updated +// compared to the entries in 'realized'. +// Here 'realized' is MapStateMap. +// This can be used to figure out which entries need to be added to or updated in 'realised'. +func (p *EndpointPolicy) UpdatedMap(realized MapStateMap) iter.Seq2[Key, MapStateEntry] { + return func(yield func(Key, MapStateEntry) bool) { + p.policyMapState.ForEach(func(key Key, entry MapStateEntry) bool { + if oldEntry, ok := realized[key]; !ok || oldEntry != entry { + if !yield(key, entry) { + return false + } + } + return true + }) + } } -func (p *EndpointPolicy) AddVisibilityKeys(e PolicyOwner, redirectPort uint16, visMeta *VisibilityMetadata, changes ChangeState) { +// Missing returns an iterator for all key/entry pairs in 'realized' that missing from 'p'. +// Here 'realized' is MapStateMap. +// This can be used to figure out which entries in 'realised' need to be deleted. +func (p *EndpointPolicy) MissingMap(realized MapStateMap) iter.Seq2[Key, MapStateEntry] { + return func(yield func(Key, MapStateEntry) bool) { + for k, v := range realized { + // If key that is in realized state is not in desired state, just remove it. + if _, ok := p.policyMapState.Get(k); !ok { + if !yield(k, v) { + break + } + } + } + } +} + +func (p *EndpointPolicy) RevertChanges(changes ChangeState) { // SelectorCache used as Identities interface which only has GetPrefix() that needs no lock - p.policyMapState.addVisibilityKeys(e, redirectPort, visMeta, changes) + p.policyMapState.revertChanges(changes) } // toMapState transforms the EndpointPolicy.L4Policy into @@ -251,43 +334,33 @@ func (p *EndpointPolicy) toMapState() { // but the Endpoint's build mutex is held. func (l4policy L4DirectionPolicy) toMapState(p *EndpointPolicy) { l4policy.PortRules.ForEach(func(l4 *L4Filter) bool { - l4.toMapState(p, l4policy.features, p.PolicyOwner.GetRealizedRedirects(), ChangeState{}) + l4.toMapState(p, l4policy.features, ChangeState{}) return true }) } -// createRedirectsFunc returns 'nil' if map changes should not be applied immemdiately, -// otherwise the returned map is to be used to find redirect ports for map updates. -type createRedirectsFunc func(*L4Filter) map[string]uint16 - -// UpdateRedirects updates redirects in the EndpointPolicy's PolicyMapState by using the provided -// function to create redirects. Changes to 'p.PolicyMapState' are collected in -// 'adds' and 'updated' so that they can be reverted when needed. -func (p *EndpointPolicy) UpdateRedirects(ingress bool, createRedirects createRedirectsFunc, changes ChangeState) { - l4policy := &p.L4Policy.Ingress - if ingress { - l4policy = &p.L4Policy.Egress +// RedirectFilters returns an iterator for each L4Filter with a redirect in the policy. +func (p *selectorPolicy) RedirectFilters() iter.Seq2[*L4Filter, *PerSelectorPolicy] { + return func(yield func(*L4Filter, *PerSelectorPolicy) bool) { + if p.L4Policy.Ingress.forEachRedirectFilter(yield) { + p.L4Policy.Egress.forEachRedirectFilter(yield) + } } - - l4policy.updateRedirects(p, createRedirects, changes) } -func (l4policy L4DirectionPolicy) updateRedirects(p *EndpointPolicy, createRedirects createRedirectsFunc, changes ChangeState) { +func (l4policy L4DirectionPolicy) forEachRedirectFilter(yield func(*L4Filter, *PerSelectorPolicy) bool) bool { + ok := true l4policy.PortRules.ForEach(func(l4 *L4Filter) bool { if l4.IsRedirect() { - // Check if we are denying this specific L4 first regardless the L3, if there are any deny policies - if l4policy.features.contains(denyRules) && p.policyMapState.deniesL4(p.PolicyOwner, l4) { - return true - } - - redirects := createRedirects(l4) - if redirects != nil { - // Set the proxy port in the policy map. - l4.toMapState(p, l4policy.features, redirects, changes) + for _, ps := range l4.PerSelectorPolicies { + if ps != nil && ps.IsRedirect() { + ok = yield(l4, ps) + } } } - return true + return ok }) + return ok } // ConsumeMapChanges transfers the changes from MapChanges to the caller. @@ -305,8 +378,8 @@ func (p *EndpointPolicy) ConsumeMapChanges() (closer func(), changes ChangeState closer = func() {} if version.IsValid() { var msg string - // update the version handle in p.VersionHandle so that any follow-on processing acts on the - // basis of the new version + // update the version handle in p.VersionHandle so that any follow-on processing + // acts on the basis of the new version if p.VersionHandle.IsValid() { p.VersionHandle.Close() msg = "ConsumeMapChanges: updated valid version" @@ -329,9 +402,9 @@ func (p *EndpointPolicy) ConsumeMapChanges() (closer func(), changes ChangeState } // NewEndpointPolicy returns an empty EndpointPolicy stub. -func NewEndpointPolicy(repo *Repository) *EndpointPolicy { +func NewEndpointPolicy(repo PolicyRepository) *EndpointPolicy { return &EndpointPolicy{ selectorPolicy: newSelectorPolicy(repo.GetSelectorCache()), - policyMapState: NewMapState(), + policyMapState: newMapState(), } } diff --git a/vendor/github.com/cilium/cilium/pkg/policy/rule.go b/vendor/github.com/cilium/cilium/pkg/policy/rule.go index 94b79cfe8e..cb83005882 100644 --- a/vendor/github.com/cilium/cilium/pkg/policy/rule.go +++ b/vendor/github.com/cilium/cilium/pkg/policy/rule.go @@ -335,7 +335,7 @@ func mergeIngressPortProto(policyCtx PolicyContext, ctx *SearchContext, endpoint return 0, err } - err = addL4Filter(policyCtx, ctx, resMap, p, proto, filterToMerge, ruleLabels) + err = addL4Filter(policyCtx, ctx, resMap, p, proto, filterToMerge) if err != nil { return 0, err } @@ -762,7 +762,7 @@ func mergeEgressPortProto(policyCtx PolicyContext, ctx *SearchContext, endpoints return 0, err } - err = addL4Filter(policyCtx, ctx, resMap, p, proto, filterToMerge, ruleLabels) + err = addL4Filter(policyCtx, ctx, resMap, p, proto, filterToMerge) if err != nil { return 0, err } diff --git a/vendor/github.com/cilium/cilium/pkg/policy/selectorcache.go b/vendor/github.com/cilium/cilium/pkg/policy/selectorcache.go index 758196f5e2..946edafa60 100644 --- a/vendor/github.com/cilium/cilium/pkg/policy/selectorcache.go +++ b/vendor/github.com/cilium/cilium/pkg/policy/selectorcache.go @@ -536,6 +536,8 @@ func (sc *SelectorCache) UpdateIdentities(added, deleted identity.IdentityMap, w if updated { // Launch a waiter that holds the new version as long as needed for users to have grabbed it + sc.queueNotifiedUsersCommit(txn, wg) + go func(version *versioned.VersionHandle) { wg.Wait() log.WithFields(logrus.Fields{ @@ -544,7 +546,6 @@ func (sc *SelectorCache) UpdateIdentities(added, deleted identity.IdentityMap, w version.Close() }(txn.GetVersionHandle()) - sc.queueNotifiedUsersCommit(txn, wg) txn.Commit() } } diff --git a/vendor/github.com/cilium/cilium/pkg/policy/trigger.go b/vendor/github.com/cilium/cilium/pkg/policy/trigger.go index 21615c1c6c..0b84674193 100644 --- a/vendor/github.com/cilium/cilium/pkg/policy/trigger.go +++ b/vendor/github.com/cilium/cilium/pkg/policy/trigger.go @@ -28,7 +28,7 @@ func (u *Updater) TriggerPolicyUpdates(force bool, reason string) { // NewUpdater returns a new Updater instance to handle triggering policy // updates ready for use. -func NewUpdater(r *Repository, regen regenerator) *Updater { +func NewUpdater(r PolicyRepository, regen regenerator) *Updater { t, err := trigger.NewTrigger(trigger.Parameters{ Name: "policy_update", MetricsObserver: &TriggerMetrics{}, @@ -62,7 +62,7 @@ func NewUpdater(r *Repository, regen regenerator) *Updater { type Updater struct { *trigger.Trigger - repo *Repository + repo PolicyRepository } type regenerator interface { diff --git a/vendor/github.com/cilium/cilium/pkg/policy/types/types.go b/vendor/github.com/cilium/cilium/pkg/policy/types/types.go index c033761666..eae47b9420 100644 --- a/vendor/github.com/cilium/cilium/pkg/policy/types/types.go +++ b/vendor/github.com/cilium/cilium/pkg/policy/types/types.go @@ -132,6 +132,8 @@ func (k Key) WithIdentity(nid identity.NumericIdentity) Key { // TrafficDirection() returns the direction of the Key, 0 == ingress, 1 == egress func (k LPMKey) TrafficDirection() trafficdirection.TrafficDirection { + // Note that 0 and 1 are the only possible return values, the shift below reduces the byte + // to a single bit. return trafficdirection.TrafficDirection(k.bits >> directionBitShift) } @@ -140,6 +142,10 @@ func (k LPMKey) PortPrefixLen() uint8 { return k.bits & ^directionBitMask } +func (k LPMKey) HasPortWildcard() bool { + return k.bits & ^directionBitMask < 16 +} + // String returns a string representation of the Key func (k Key) String() string { dPort := strconv.FormatUint(uint64(k.DestPort), 10) diff --git a/vendor/github.com/cilium/cilium/pkg/policy/visibility.go b/vendor/github.com/cilium/cilium/pkg/policy/visibility.go deleted file mode 100644 index 8a8a72c911..0000000000 --- a/vendor/github.com/cilium/cilium/pkg/policy/visibility.go +++ /dev/null @@ -1,235 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of Cilium - -package policy - -import ( - "fmt" - "regexp" - "strconv" - "strings" - - ciliumio "github.com/cilium/cilium/pkg/k8s/apis/cilium.io" - "github.com/cilium/cilium/pkg/labels" - "github.com/cilium/cilium/pkg/policy/api" - "github.com/cilium/cilium/pkg/u8proto" -) - -var ( - singleAnnotationRegex = "<(Ingress|Egress)/([1-9][0-9]{1,5})/(TCP|UDP|SCTP|ANY)/([A-Za-z]{3,32})>" - annotationRegex = regexp.MustCompile(fmt.Sprintf(`^((%s)(,(%s))*)$`, singleAnnotationRegex, singleAnnotationRegex)) -) - -func validateL7ProtocolWithDirection(dir string, proto L7ParserType) error { - switch proto { - case ParserTypeHTTP: - return nil - case ParserTypeDNS: - if dir == "Egress" { - return nil - } - case ParserTypeKafka: - return nil - default: - return fmt.Errorf("unsupported parser type %s", proto) - - } - return fmt.Errorf("%s not allowed with direction %s", proto, dir) -} - -// NewVisibilityPolicy generates the VisibilityPolicy that is encoded in the -// annotation parameter. -// Returns an error: -// - if the annotation does not correspond to the expected -// format for a visibility annotation. -// - if there is a conflict between the state encoded in the annotation (e.g., -// different L7 protocols for the same L4 port / protocol / traffic direction. -func NewVisibilityPolicy(anno, namespace, pod string) (*VisibilityPolicy, error) { - if !annotationRegex.MatchString(anno) { - return nil, fmt.Errorf("annotation for proxy visibility did not match expected format %s", annotationRegex.String()) - } - - nvp := &VisibilityPolicy{ - Ingress: make(DirectionalVisibilityPolicy), - Egress: make(DirectionalVisibilityPolicy), - } - - // TODO: look into using regex groups. - anSplit := strings.Split(anno, ",") - for i := range anSplit { - proxyAnnoSplit := strings.Split(anSplit[i], "/") - if len(proxyAnnoSplit) != 4 { - err := fmt.Errorf("invalid number of fields (%d) in annotation", len(proxyAnnoSplit)) - return nil, err - } - // Ingress|Egress - // Don't need to validate the content itself, regex already did that. - direction := proxyAnnoSplit[0][1:] - port := proxyAnnoSplit[1] - - portInt, err := strconv.ParseUint(port, 10, 16) - if err != nil { - return nil, fmt.Errorf("unable to parse port: %w", err) - } - - // Don't need to validate, regex already did that. - l4Proto := proxyAnnoSplit[2] - u8Prot, err := u8proto.ParseProtocol(l4Proto) - if err != nil { - return nil, fmt.Errorf("invalid L4 protocol %s", l4Proto) - } - - // ANY equates to TCP and UDP in the datapath; the datapath itself does - // not support 'Any' protocol paired with a port at L4. - var protos []u8proto.U8proto - if u8Prot == u8proto.ANY { - protos = append(protos, u8proto.TCP) - protos = append(protos, u8proto.UDP) - protos = append(protos, u8proto.SCTP) - } else { - protos = append(protos, u8Prot) - } - // Remove trailing '>'. - l7Protocol := L7ParserType(strings.ToLower(proxyAnnoSplit[3][:len(proxyAnnoSplit[3])-1])) - - if err := validateL7ProtocolWithDirection(direction, l7Protocol); err != nil { - return nil, err - } - - var dvp DirectionalVisibilityPolicy - var ingress bool - if direction == "Ingress" { - dvp = nvp.Ingress - ingress = true - } else { - dvp = nvp.Egress - ingress = false - } - - for _, prot := range protos { - pp := strconv.FormatUint(portInt, 10) + "/" + prot.String() - if res, ok := dvp[pp]; ok { - if res.Parser != l7Protocol { - return nil, fmt.Errorf("duplicate annotations with different L7 protocols %s and %s for %s", res.Parser, l7Protocol, pp) - } - } - - l7Meta := generateL7AllowAllRules(l7Protocol, namespace, pod) - - dvp[pp] = &VisibilityMetadata{ - Parser: l7Protocol, - Port: uint16(portInt), - Proto: prot, - Ingress: ingress, - L7Metadata: l7Meta, - } - } - } - - return nvp, nil -} - -func generateL7AllowAllRules(parser L7ParserType, namespace, pod string) L7DataMap { - var m L7DataMap - switch parser { - case ParserTypeDNS: - m = L7DataMap{} - // Create an entry to explicitly allow all at L7 for DNS. - emptyL3Selector := &identitySelector{source: &labelIdentitySelector{selector: api.WildcardEndpointSelector}, key: wildcardSelectorKey} - emptyL3Selector.metadataLbls = labels.LabelArray{ - labels.NewLabel(ciliumio.PolicyLabelDerivedFrom, "PodVisibilityAnnotation", labels.LabelSourceK8s), - } - if namespace != "" { - emptyL3Selector.metadataLbls = append(emptyL3Selector.metadataLbls, labels.NewLabel(ciliumio.PodNamespaceLabel, namespace, labels.LabelSourceK8s)) - } - if pod != "" { - emptyL3Selector.metadataLbls = append(emptyL3Selector.metadataLbls, labels.NewLabel(ciliumio.PodNameLabel, pod, labels.LabelSourceK8s)) - } - - m[emptyL3Selector] = &PerSelectorPolicy{ - L7Rules: api.L7Rules{ - DNS: []api.PortRuleDNS{ - { - MatchPattern: "*", - }, - }, - }, - } - } - return m -} - -// VisibilityMetadata encodes state about what type of traffic should be -// redirected to an L7Proxy. Implements the ProxyPolicy interface. -// TODO: an L4Filter could be composed of this type. -type VisibilityMetadata struct { - // Parser represents the proxy to which traffic should be redirected. - Parser L7ParserType - - // Port, in tandem with Proto, signifies which L4 port for which traffic - // should be redirected. - Port uint16 - - // Proto, in tandem with port, signifies which L4 protocol for which traffic - // should be redirected. - Proto u8proto.U8proto - - // Ingress specifies whether ingress traffic at the given L4 port / protocol - // should be redirected to the proxy. - Ingress bool - - // L7Metadata encodes optional information what is allowed at L7 for - // visibility. Some specific protocol parsers do not need this set for - // allowing of traffic (e.g., HTTP), but some do (e.g., DNS). - L7Metadata L7DataMap -} - -// DirectionalVisibilityPolicy is a mapping of VisibilityMetadata keyed by -// L4 Port / L4 Protocol (e.g., 80/TCP) for a given traffic direction (e.g., -// ingress or egress). This encodes at which L4 Port / L4 Protocol traffic -// should be redirected to a given L7 proxy. An empty instance of this type -// indicates that no traffic should be redirected. -type DirectionalVisibilityPolicy map[string]*VisibilityMetadata - -// VisibilityPolicy represents for both ingress and egress which types of -// traffic should be redirected to a given L7 proxy. -type VisibilityPolicy struct { - Ingress DirectionalVisibilityPolicy - Egress DirectionalVisibilityPolicy - Error error -} - -// CopyL7RulesPerEndpoint returns a shallow copy of the L7Metadata of the -// L4Filter. -func (v *VisibilityMetadata) CopyL7RulesPerEndpoint() L7DataMap { - if v.L7Metadata != nil { - return v.L7Metadata.ShallowCopy() - } - return nil -} - -// GetL7Parser returns the L7ParserType for this VisibilityMetadata. -func (v *VisibilityMetadata) GetL7Parser() L7ParserType { - return v.Parser -} - -// GetIngress returns whether the VisibilityMetadata applies at ingress or -// egress. -func (v *VisibilityMetadata) GetIngress() bool { - return v.Ingress -} - -// GetPort returns at which port the VisibilityMetadata applies. -func (v *VisibilityMetadata) GetPort() uint16 { - return v.Port -} - -// GetProtocol returns the protocol where the VisibilityMetadata applies. -func (v *VisibilityMetadata) GetProtocol() u8proto.U8proto { - return v.Proto -} - -// GetListener returns the optional listener name. -func (l4 *VisibilityMetadata) GetListener() string { - return "" -} diff --git a/vendor/github.com/cilium/cilium/pkg/resiliency/error.go b/vendor/github.com/cilium/cilium/pkg/resiliency/error.go new file mode 100644 index 0000000000..cd348fc00e --- /dev/null +++ b/vendor/github.com/cilium/cilium/pkg/resiliency/error.go @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Cilium + +package resiliency + +// retryableErr tracks errors that could be retried. +type retryableErr struct { + error +} + +// Retryable returns a new instance. +func Retryable(e error) retryableErr { + return retryableErr{error: e} +} diff --git a/vendor/github.com/cilium/cilium/pkg/resiliency/errorset.go b/vendor/github.com/cilium/cilium/pkg/resiliency/errorset.go new file mode 100644 index 0000000000..9ac59e72d2 --- /dev/null +++ b/vendor/github.com/cilium/cilium/pkg/resiliency/errorset.go @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Cilium + +package resiliency + +import ( + "errors" + "fmt" +) + +type tuple struct { + index int + err error +} + +// ErrorSet tracks a collection of unique errors. +type ErrorSet struct { + total, failed int + msg string + errs map[string]tuple +} + +// NewErrorSet returns a new instance. +func NewErrorSet(msg string, c int) *ErrorSet { + return &ErrorSet{ + msg: msg, + total: c, + errs: make(map[string]tuple), + } +} + +// Add adds one or more errors to the set. +func (e *ErrorSet) Add(errs ...error) { + for _, err := range errs { + if err == nil { + continue + } + if _, ok := e.errs[err.Error()]; ok { + continue + } + e.errs[err.Error()] = tuple{index: e.failed, err: err} + e.failed++ + } +} + +// Error returns a list of unique errors or nil. +func (e *ErrorSet) Errors() []error { + if len(e.errs) == 0 { + return nil + } + errs := make([]error, len(e.errs)+1) + errs[0] = fmt.Errorf("%s (%d/%d) failed", e.msg, e.failed, e.total) + for _, t := range e.errs { + errs[t.index+1] = t.err + } + + return errs +} + +// Error returns a new composite error or nil. +func (e *ErrorSet) Error() error { + return errors.Join(e.Errors()...) +} diff --git a/vendor/github.com/cilium/cilium/pkg/resiliency/helpers.go b/vendor/github.com/cilium/cilium/pkg/resiliency/helpers.go new file mode 100644 index 0000000000..0cd5d9cb8a --- /dev/null +++ b/vendor/github.com/cilium/cilium/pkg/resiliency/helpers.go @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Cilium + +package resiliency + +import ( + "context" + "time" + + "k8s.io/apimachinery/pkg/util/wait" +) + +// RetryFunc tracks resiliency retry calls. +type RetryFunc func(ctx context.Context, retries int) (bool, error) + +// Retry retries the provided call using exponential retries given an initial duration for up to max retries count. +func Retry(ctx context.Context, duration time.Duration, maxRetries int, fn RetryFunc) error { + bo := wait.Backoff{ + Duration: duration, + Factor: 1, + Jitter: 0.1, + Steps: maxRetries, + } + + var retries int + f := func(ctx context.Context) (bool, error) { + retries++ + return fn(ctx, retries) + } + + return wait.ExponentialBackoffWithContext(ctx, bo, f) +} diff --git a/vendor/github.com/cilium/cilium/pkg/resiliency/retry.go b/vendor/github.com/cilium/cilium/pkg/resiliency/retry.go new file mode 100644 index 0000000000..a6f1bc6344 --- /dev/null +++ b/vendor/github.com/cilium/cilium/pkg/resiliency/retry.go @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Cilium + +package resiliency + +import ( + "errors" +) + +// IsRetryable checks if an error can be retried. +func IsRetryable(e error) bool { + return errors.As(e, new(retryableErr)) +} diff --git a/vendor/github.com/cilium/cilium/pkg/slices/slices.go b/vendor/github.com/cilium/cilium/pkg/slices/slices.go index 7cd2e860e4..9e81fb9551 100644 --- a/vendor/github.com/cilium/cilium/pkg/slices/slices.go +++ b/vendor/github.com/cilium/cilium/pkg/slices/slices.go @@ -84,24 +84,6 @@ func SortedUnique[S ~[]T, T cmp.Ordered](s S) S { return slices.Compact(s) } -// SortedUniqueFunc is like SortedUnique but allows the user to specify custom functions -// for ordering (less function) and comparing (eq function) the elements in the slice. -// This is useful in all the cases where SortedUnique cannot be used: -// - for types that do not satisfy constraints.Ordered (e.g: composite types) -// - when the user wants to customize how elements are compared (e.g: user wants to enforce reverse ordering) -func SortedUniqueFunc[S ~[]T, T any]( - s S, - less func(a, b T) int, - eq func(a, b T) bool, -) S { - if len(s) < 2 { - return s - } - - slices.SortFunc(s, less) - return slices.CompactFunc(s, eq) -} - // Diff returns a slice of elements which is the difference of a and b. // The returned slice keeps the elements in the same order found in the "a" slice. // Both input slices are considered as sets, that is, all elements are considered as @@ -149,3 +131,15 @@ func XorNil[T any](s1, s2 []T) bool { return s1 == nil && s2 != nil || s1 != nil && s2 == nil } + +// AllMatch returns true if pred is true for each element in s, false otherwise. +// May not evaluate on all elements if not necessary for determining the result. +// If the slice is empty then true is returned and predicate is not evaluated. +func AllMatch[T any](s []T, pred func(v T) bool) bool { + for _, v := range s { + if !pred(v) { + return false + } + } + return true +} diff --git a/vendor/github.com/cilium/cilium/pkg/trigger/trigger.go b/vendor/github.com/cilium/cilium/pkg/trigger/trigger.go index 7afae25872..48c89e9ad0 100644 --- a/vendor/github.com/cilium/cilium/pkg/trigger/trigger.go +++ b/vendor/github.com/cilium/cilium/pkg/trigger/trigger.go @@ -6,7 +6,6 @@ package trigger import ( "fmt" - "github.com/cilium/cilium/pkg/inctimer" "github.com/cilium/cilium/pkg/lock" "github.com/cilium/cilium/pkg/time" ) @@ -173,8 +172,9 @@ func (t *Trigger) Shutdown() { } func (t *Trigger) waiter() { - sleepTimer, sleepTimerDone := inctimer.New() - defer sleepTimerDone() + tk := time.NewTicker(t.params.sleepInterval) + defer tk.Stop() + for { // keep critical section as small as possible t.mutex.Lock() @@ -208,8 +208,7 @@ func (t *Trigger) waiter() { select { case <-t.wakeupChan: - case <-sleepTimer.After(t.params.sleepInterval): - + case <-tk.C: case <-t.closeChan: shutdownFunc := t.params.ShutdownFunc if shutdownFunc != nil { diff --git a/vendor/github.com/cilium/hive/cell/lifecycle.go b/vendor/github.com/cilium/hive/cell/lifecycle.go index cbe41eebd9..a41f75ee2c 100644 --- a/vendor/github.com/cilium/hive/cell/lifecycle.go +++ b/vendor/github.com/cilium/hive/cell/lifecycle.go @@ -75,6 +75,19 @@ type augmentedHook struct { moduleID FullModuleID } +func NewDefaultLifecycle(hooks []HookInterface, numStarted int, logThreshold time.Duration) *DefaultLifecycle { + h := make([]augmentedHook, 0, len(hooks)) + for _, hook := range hooks { + h = append(h, augmentedHook{hook, nil}) + } + return &DefaultLifecycle{ + mu: sync.Mutex{}, + hooks: h, + numStarted: numStarted, + LogThreshold: logThreshold, + } +} + func (lc *DefaultLifecycle) Append(hook HookInterface) { lc.mu.Lock() defer lc.mu.Unlock() @@ -92,7 +105,7 @@ func (lc *DefaultLifecycle) Start(log *slog.Logger, ctx context.Context) error { ctx, cancel := context.WithCancel(ctx) defer cancel() - for _, hook := range lc.hooks { + for i, hook := range lc.hooks { fnName, exists := getHookFuncName(hook, true) if !exists { @@ -102,6 +115,13 @@ func (lc *DefaultLifecycle) Start(log *slog.Logger, ctx context.Context) error { } l := log.With("function", fnName) + + // Do not attempt to start already started hooks. + if i < lc.numStarted { + l.Error("Hook appears to be running. Skipping") + continue + } + l.Debug("Executing start hook") t0 := time.Now() if err := hook.Start(ctx); err != nil { diff --git a/vendor/github.com/cilium/hive/cell/simple_health.go b/vendor/github.com/cilium/hive/cell/simple_health.go index 49806c1540..60da0ef46d 100644 --- a/vendor/github.com/cilium/hive/cell/simple_health.go +++ b/vendor/github.com/cilium/hive/cell/simple_health.go @@ -1,7 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Cilium + package cell import ( + "fmt" + "regexp" "sync" + "time" + + "github.com/cilium/hive/script" ) type simpleHealthRoot struct { @@ -87,6 +95,57 @@ func NewSimpleHealth() (Health, *SimpleHealth) { return h, h } +// SimpleHealthCmd for showing or checking the simple module health state. +// Not provided as hive.ScriptCmdOut due to cyclic import issues. To include +// provide with: hive.ScriptCmdOut("health", SimpleHealthCmd(simpleHealth))) +// +// Example: +// +// # show health +// health +// +// # grep health +// health 'my-module: level=OK' +func SimpleHealthCmd(h *SimpleHealth) script.Cmd { + return script.Command( + script.CmdUsage{ + Summary: "Show or grep simple health", + Args: "(pattern)", + }, + func(s *script.State, args ...string) (script.WaitFunc, error) { + var re *regexp.Regexp + if len(args) == 1 { + re = regexp.MustCompile(args[0]) + } + for s.Context().Err() == nil { + h.Lock() + for name, h := range h.all { + var errStr string + if h.Error != nil { + errStr = h.Error.Error() + } + line := fmt.Sprintf("%s: level=%s message=%s error=%s", name, h.Level, h.Status, errStr) + if re != nil { + if re.Match([]byte(line)) { + h.Unlock() + s.Logf("matched: %s\n", line) + return nil, nil + } + } else { + fmt.Fprintln(s.LogWriter(), line) + } + } + h.Unlock() + if re == nil { + return nil, nil + } + time.Sleep(10 * time.Millisecond) + } + return nil, fmt.Errorf("no match for %s", re) + }, + ) +} + var _ Health = &SimpleHealth{} var SimpleHealthCell = Provide(NewSimpleHealth) diff --git a/vendor/github.com/cilium/hive/hive.go b/vendor/github.com/cilium/hive/hive.go index 34e17b600b..4dd80d6afa 100644 --- a/vendor/github.com/cilium/hive/hive.go +++ b/vendor/github.com/cilium/hive/hive.go @@ -20,6 +20,7 @@ import ( "go.uber.org/dig" "github.com/cilium/hive/cell" + "github.com/cilium/hive/script" ) type Options struct { @@ -421,3 +422,19 @@ func (h *Hive) getEnvName(option string) string { upper := strings.ToUpper(under) return h.opts.EnvPrefix + upper } + +func (h *Hive) ScriptCommands(log *slog.Logger) (map[string]script.Cmd, error) { + if err := h.Populate(log); err != nil { + return nil, fmt.Errorf("failed to populate object graph: %s", err) + } + m := map[string]script.Cmd{} + m["hive"] = hiveScriptCmd(h, log) + + // Gather the commands from the hive. + h.container.Invoke(func(sc ScriptCmds) { + for name, cmd := range sc.Map() { + m[name] = cmd + } + }) + return m, nil +} diff --git a/vendor/github.com/cilium/hive/script.go b/vendor/github.com/cilium/hive/script.go new file mode 100644 index 0000000000..72d1e782a5 --- /dev/null +++ b/vendor/github.com/cilium/hive/script.go @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Cilium + +package hive + +import ( + "context" + "errors" + "fmt" + "io" + "log/slog" + "os" + "os/signal" + "time" + + "github.com/cilium/hive/cell" + "github.com/cilium/hive/script" + "golang.org/x/term" +) + +func NewScriptCmd(name string, cmd script.Cmd) ScriptCmdOut { + return ScriptCmdOut{ScriptCmd: ScriptCmd{name, cmd}} +} + +func NewScriptCmds(cmds map[string]script.Cmd) (out ScriptCmdsOut) { + out.ScriptCmds = make([]ScriptCmd, 0, len(cmds)) + for name, cmd := range cmds { + out.ScriptCmds = append(out.ScriptCmds, ScriptCmd{name, cmd}) + } + return out +} + +type ScriptCmd struct { + Name string + Cmd script.Cmd +} + +type ScriptCmds struct { + cell.In + + ScriptCmds []ScriptCmd `group:"script-commands"` +} + +func (sc ScriptCmds) Map() map[string]script.Cmd { + m := make(map[string]script.Cmd, len(sc.ScriptCmds)) + for _, c := range sc.ScriptCmds { + m[c.Name] = c.Cmd + } + return m +} + +type ScriptCmdOut struct { + cell.Out + + ScriptCmd ScriptCmd `group:"script-commands"` +} + +type ScriptCmdsOut struct { + cell.Out + + ScriptCmds []ScriptCmd `group:"script-commands,flatten"` +} + +func hiveScriptCmd(h *Hive, log *slog.Logger) script.Cmd { + const defaultTimeout = time.Minute + return script.Command( + script.CmdUsage{ + Summary: "manipulate the hive", + Args: "cmd args...", + }, + func(s *script.State, args ...string) (script.WaitFunc, error) { + if len(args) < 1 { + return nil, fmt.Errorf("hive cmd args...\n'cmd' is one of: start, stop, jobs") + } + switch args[0] { + case "start": + ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) + defer cancel() + return nil, h.Start(log, ctx) + case "stop": + ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) + defer cancel() + return nil, h.Stop(log, ctx) + } + return nil, fmt.Errorf("unknown hive command %q, expected one of: start, stop, jobs", args[0]) + }, + ) +} + +func RunRepl(h *Hive, in *os.File, out *os.File, prompt string) { + // Try to set the input into raw mode. + restore, err := script.MakeRaw(int(in.Fd())) + defer restore() + + inout := struct { + io.Reader + io.Writer + }{in, out} + term := term.NewTerminal(inout, prompt) + log := slog.New(slog.NewTextHandler(term, nil)) + + cmds, err := h.ScriptCommands(log) + if err != nil { + log.Error("ScriptCommands()", "error", err) + return + } + for name, cmd := range script.DefaultCmds() { + cmds[name] = cmd + } + + e := script.Engine{ + Cmds: cmds, + Conds: nil, + } + + stop := make(chan struct{}) + defer close(stop) + + sigs := make(chan os.Signal, 1) + defer signal.Stop(sigs) + signal.Notify(sigs, os.Interrupt) + + newState := func() *script.State { + ctx, cancel := context.WithCancel(context.Background()) + s, err := script.NewState(ctx, "/tmp", nil) + if err != nil { + panic(err) + } + go func() { + select { + case <-stop: + cancel() + case <-sigs: + cancel() + } + }() + return s + } + + s := newState() + + for { + line, err := term.ReadLine() + if err != nil { + if errors.Is(err, io.EOF) { + return + } else { + panic(err) + } + } + + err = e.ExecuteLine(s, line, term) + if err != nil { + fmt.Fprintln(term, err.Error()) + } + + if s.Context().Err() != nil { + // Context was cancelled due to interrupt. Re-create the state + // to run more commands. + s = newState() + fmt.Fprintln(term, "^C (interrupted)") + } + } +} diff --git a/vendor/github.com/cilium/hive/script/LICENSE b/vendor/github.com/cilium/hive/script/LICENSE new file mode 100644 index 0000000000..6a66aea5ea --- /dev/null +++ b/vendor/github.com/cilium/hive/script/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/cilium/hive/script/README.md b/vendor/github.com/cilium/hive/script/README.md new file mode 100644 index 0000000000..6b6da3a3fa --- /dev/null +++ b/vendor/github.com/cilium/hive/script/README.md @@ -0,0 +1,4 @@ +This is a fork of rsc.io/script (v0.0.2). It mostly adds support for interactive use to it. + +The makeraw* files are adapted from term_unix.go etc. files from x/term to enable interrupts. + diff --git a/vendor/github.com/cilium/hive/script/README.md.original b/vendor/github.com/cilium/hive/script/README.md.original new file mode 100644 index 0000000000..0716f680e3 --- /dev/null +++ b/vendor/github.com/cilium/hive/script/README.md.original @@ -0,0 +1,11 @@ +This is a copy of cmd/go/internal/script. + +See and . +Posting it here makes it available for others to try +without us committing to officially supporting it. +We have been using it in the go command for many years now; +the code is quite stable. +Ironically, it has very few tests. + + is a port +of an earlier version of the go command script language. diff --git a/vendor/github.com/cilium/hive/script/cmds.go b/vendor/github.com/cilium/hive/script/cmds.go new file mode 100644 index 0000000000..acea4128bf --- /dev/null +++ b/vendor/github.com/cilium/hive/script/cmds.go @@ -0,0 +1,1167 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package script + +import ( + "errors" + "fmt" + "io/fs" + "os" + "os/exec" + "path/filepath" + "regexp" + "runtime" + "strconv" + "strings" + "sync" + "time" + + "github.com/cilium/hive/script/internal/diff" + "golang.org/x/term" +) + +// DefaultCmds returns a set of broadly useful script commands. +// +// Run the 'help' command within a script engine to view a list of the available +// commands. +func DefaultCmds() map[string]Cmd { + return map[string]Cmd{ + "cat": Cat(), + "cd": Cd(), + "chmod": Chmod(), + "cmp": Cmp(), + "cmpenv": Cmpenv(), + "cp": Cp(), + "echo": Echo(), + "env": Env(), + "exec": Exec(func(cmd *exec.Cmd) error { return cmd.Process.Signal(os.Interrupt) }, 100*time.Millisecond), // arbitrary grace period + "exists": Exists(), + "grep": Grep(), + "help": Help(), + "mkdir": Mkdir(), + "mv": Mv(), + "rm": Rm(), + "replace": Replace(), + "sleep": Sleep(), + "stderr": Stderr(), + "stdout": Stdout(), + "stop": Stop(), + "symlink": Symlink(), + "wait": Wait(), + "break": Break(), + } +} + +// Command returns a new Cmd with a Usage method that returns a copy of the +// given CmdUsage and a Run method calls the given function. +func Command(usage CmdUsage, run func(*State, ...string) (WaitFunc, error)) Cmd { + return &funcCmd{ + usage: usage, + run: run, + } +} + +// A funcCmd implements Cmd using a function value. +type funcCmd struct { + usage CmdUsage + run func(*State, ...string) (WaitFunc, error) +} + +func (c *funcCmd) Run(s *State, args ...string) (WaitFunc, error) { + return c.run(s, args...) +} + +func (c *funcCmd) Usage() *CmdUsage { return &c.usage } + +// firstNonFlag returns a slice containing the index of the first argument in +// rawArgs that is not a flag, or nil if all arguments are flags. +func firstNonFlag(rawArgs ...string) []int { + for i, arg := range rawArgs { + if !strings.HasPrefix(arg, "-") { + return []int{i} + } + if arg == "--" { + return []int{i + 1} + } + } + return nil +} + +// Cat writes the concatenated contents of the named file(s) to the script's +// stdout buffer. +func Cat() Cmd { + return Command( + CmdUsage{ + Summary: "concatenate files and print to the script's stdout buffer", + Args: "files...", + }, + func(s *State, args ...string) (WaitFunc, error) { + if len(args) == 0 { + return nil, ErrUsage + } + + paths := make([]string, 0, len(args)) + for _, arg := range args { + paths = append(paths, s.Path(arg)) + } + + var buf strings.Builder + errc := make(chan error, 1) + go func() { + for _, p := range paths { + b, err := os.ReadFile(p) + buf.Write(b) + if err != nil { + errc <- err + return + } + } + errc <- nil + }() + + wait := func(*State) (stdout, stderr string, err error) { + err = <-errc + return buf.String(), "", err + } + return wait, nil + }) +} + +// Cd changes the current working directory. +func Cd() Cmd { + return Command( + CmdUsage{ + Summary: "change the working directory", + Args: "dir", + }, + func(s *State, args ...string) (WaitFunc, error) { + if len(args) != 1 { + return nil, ErrUsage + } + return nil, s.Chdir(args[0]) + }) +} + +// Chmod changes the permissions of a file or a directory.. +func Chmod() Cmd { + return Command( + CmdUsage{ + Summary: "change file mode bits", + Args: "perm paths...", + Detail: []string{ + "Changes the permissions of the named files or directories to be equal to perm.", + "Only numerical permissions are supported.", + }, + }, + func(s *State, args ...string) (WaitFunc, error) { + if len(args) < 2 { + return nil, ErrUsage + } + + perm, err := strconv.ParseUint(args[0], 0, 32) + if err != nil || perm&uint64(fs.ModePerm) != perm { + return nil, fmt.Errorf("invalid mode: %s", args[0]) + } + + for _, arg := range args[1:] { + err := os.Chmod(s.Path(arg), fs.FileMode(perm)) + if err != nil { + return nil, err + } + } + return nil, nil + }) +} + +// Cmp compares the contents of two files, or the contents of either the +// "stdout" or "stderr" buffer and a file, returning a non-nil error if the +// contents differ. +func Cmp() Cmd { + return Command( + CmdUsage{ + Args: "[-q] file1 file2", + Summary: "compare files for differences", + Detail: []string{ + "By convention, file1 is the actual data and file2 is the expected data.", + "The command succeeds if the file contents are identical.", + "File1 can be 'stdout' or 'stderr' to compare the stdout or stderr buffer from the most recent command.", + }, + }, + func(s *State, args ...string) (WaitFunc, error) { + return nil, doCompare(s, false, args...) + }) +} + +// Cmpenv is like Compare, but also performs environment substitutions +// on the contents of both arguments. +func Cmpenv() Cmd { + return Command( + CmdUsage{ + Args: "[-q] file1 file2", + Summary: "compare files for differences, with environment expansion", + Detail: []string{ + "By convention, file1 is the actual data and file2 is the expected data.", + "The command succeeds if the file contents are identical after substituting variables from the script environment.", + "File1 can be 'stdout' or 'stderr' to compare the script's stdout or stderr buffer.", + }, + }, + func(s *State, args ...string) (WaitFunc, error) { + return nil, doCompare(s, true, args...) + }) +} + +func doCompare(s *State, env bool, args ...string) error { + quiet := false + if len(args) > 0 && args[0] == "-q" { + quiet = true + args = args[1:] + } + if len(args) != 2 { + return ErrUsage + } + + name1, name2 := args[0], args[1] + var text1, text2 string + switch name1 { + case "stdout": + text1 = s.Stdout() + case "stderr": + text1 = s.Stderr() + default: + data, err := os.ReadFile(s.Path(name1)) + if err != nil { + return err + } + text1 = string(data) + } + + data, err := os.ReadFile(s.Path(name2)) + if err != nil { + return err + } + text2 = string(data) + + if env { + text1 = s.ExpandEnv(text1, false) + text2 = s.ExpandEnv(text2, false) + } + + if text1 != text2 { + if !quiet { + diffText := diff.Diff(name1, []byte(text1), name2, []byte(text2)) + s.Logf("%s\n", diffText) + } + return fmt.Errorf("%s and %s differ", name1, name2) + } + return nil +} + +// Cp copies one or more files to a new location. +func Cp() Cmd { + return Command( + CmdUsage{ + Summary: "copy files to a target file or directory", + Args: "src... dst", + Detail: []string{ + "src can include 'stdout' or 'stderr' to copy from the script's stdout or stderr buffer.", + }, + }, + func(s *State, args ...string) (WaitFunc, error) { + if len(args) < 2 { + return nil, ErrUsage + } + + dst := s.Path(args[len(args)-1]) + info, err := os.Stat(dst) + dstDir := err == nil && info.IsDir() + if len(args) > 2 && !dstDir { + return nil, &fs.PathError{Op: "cp", Path: dst, Err: errors.New("destination is not a directory")} + } + + for _, arg := range args[:len(args)-1] { + var ( + src string + data []byte + mode fs.FileMode + ) + switch arg { + case "stdout": + src = arg + data = []byte(s.Stdout()) + mode = 0666 + case "stderr": + src = arg + data = []byte(s.Stderr()) + mode = 0666 + default: + src = s.Path(arg) + info, err := os.Stat(src) + if err != nil { + return nil, err + } + mode = info.Mode() & 0777 + data, err = os.ReadFile(src) + if err != nil { + return nil, err + } + } + targ := dst + if dstDir { + targ = filepath.Join(dst, filepath.Base(src)) + } + err := os.WriteFile(targ, data, mode) + if err != nil { + return nil, err + } + } + + return nil, nil + }) +} + +// Echo writes its arguments to stdout, followed by a newline. +func Echo() Cmd { + return Command( + CmdUsage{ + Summary: "display a line of text", + Args: "string...", + }, + func(s *State, args ...string) (WaitFunc, error) { + var buf strings.Builder + for i, arg := range args { + if i > 0 { + buf.WriteString(" ") + } + buf.WriteString(arg) + } + buf.WriteString("\n") + out := buf.String() + + // Stuff the result into a callback to satisfy the OutputCommandFunc + // interface, even though it isn't really asynchronous even if run in the + // background. + // + // Nobody should be running 'echo' as a background command, but it's not worth + // defining yet another interface, and also doesn't seem worth shoehorning + // into a SimpleCommand the way we did with Wait. + return func(*State) (stdout, stderr string, err error) { + return out, "", nil + }, nil + }) +} + +// Env sets or logs the values of environment variables. +// +// With no arguments, Env reports all variables in the environment. +// "key=value" arguments set variables, and arguments without "=" +// cause the corresponding value to be printed to the stdout buffer. +func Env() Cmd { + return Command( + CmdUsage{ + Summary: "set or log the values of environment variables", + Args: "[key[=value]...]", + Detail: []string{ + "With no arguments, print the script environment to the log.", + "Otherwise, add the listed key=value pairs to the environment or print the listed keys.", + }, + }, + func(s *State, args ...string) (WaitFunc, error) { + out := new(strings.Builder) + if len(args) == 0 { + for _, kv := range s.env { + fmt.Fprintf(out, "%s\n", kv) + } + } else { + for _, env := range args { + i := strings.Index(env, "=") + if i < 0 { + // Display value instead of setting it. + fmt.Fprintf(out, "%s=%s\n", env, s.envMap[env]) + continue + } + if err := s.Setenv(env[:i], env[i+1:]); err != nil { + return nil, err + } + } + } + var wait WaitFunc + if out.Len() > 0 || len(args) == 0 { + wait = func(*State) (stdout, stderr string, err error) { + return out.String(), "", nil + } + } + return wait, nil + }) +} + +// Exec runs an arbitrary executable as a subprocess. +// +// When the Script's context is canceled, Exec sends the interrupt signal, then +// waits for up to the given delay for the subprocess to flush output before +// terminating it with os.Kill. +func Exec(cancel func(*exec.Cmd) error, waitDelay time.Duration) Cmd { + return Command( + CmdUsage{ + Summary: "run an executable program with arguments", + Args: "program [args...]", + Detail: []string{ + "Note that 'exec' does not terminate the script (unlike Unix shells).", + }, + Async: true, + }, + func(s *State, args ...string) (WaitFunc, error) { + if len(args) < 1 { + return nil, ErrUsage + } + + // Use the script's PATH to look up the command (if it does not contain a separator) + // instead of the test process's PATH (see lookPath). + // Don't use filepath.Clean, since that changes "./foo" to "foo". + name := filepath.FromSlash(args[0]) + path := name + if !strings.Contains(name, string(filepath.Separator)) { + var err error + path, err = lookPath(s, name) + if err != nil { + return nil, err + } + } + + return startCommand(s, name, path, args[1:], cancel, waitDelay) + }) +} + +func startCommand(s *State, name, path string, args []string, cancel func(*exec.Cmd) error, waitDelay time.Duration) (WaitFunc, error) { + var ( + cmd *exec.Cmd + stdoutBuf, stderrBuf strings.Builder + ) + for { + cmd = exec.CommandContext(s.Context(), path, args...) + if cancel == nil { + cmd.Cancel = nil + } else { + cmd.Cancel = func() error { return cancel(cmd) } + } + cmd.WaitDelay = waitDelay + cmd.Args[0] = name + cmd.Dir = s.Getwd() + cmd.Env = s.env + cmd.Stdout = &stdoutBuf + cmd.Stderr = &stderrBuf + err := cmd.Start() + if err == nil { + break + } + if isETXTBSY(err) { + // If the script (or its host process) just wrote the executable we're + // trying to run, a fork+exec in another thread may be holding open the FD + // that we used to write the executable (see https://go.dev/issue/22315). + // Since the descriptor should have CLOEXEC set, the problem should + // resolve as soon as the forked child reaches its exec call. + // Keep retrying until that happens. + } else { + return nil, err + } + } + + wait := func(s *State) (stdout, stderr string, err error) { + err = cmd.Wait() + return stdoutBuf.String(), stderrBuf.String(), err + } + return wait, nil +} + +// lookPath is (roughly) like exec.LookPath, but it uses the script's current +// PATH to find the executable. +func lookPath(s *State, command string) (string, error) { + var strEqual func(string, string) bool + if runtime.GOOS == "windows" || runtime.GOOS == "darwin" { + // Using GOOS as a proxy for case-insensitive file system. + // TODO(bcmills): Remove this assumption. + strEqual = strings.EqualFold + } else { + strEqual = func(a, b string) bool { return a == b } + } + + var pathExt []string + var searchExt bool + var isExecutable func(os.FileInfo) bool + if runtime.GOOS == "windows" { + // Use the test process's PathExt instead of the script's. + // If PathExt is set in the command's environment, cmd.Start fails with + // "parameter is invalid". Not sure why. + // If the command already has an extension in PathExt (like "cmd.exe") + // don't search for other extensions (not "cmd.bat.exe"). + pathExt = strings.Split(os.Getenv("PathExt"), string(filepath.ListSeparator)) + searchExt = true + cmdExt := filepath.Ext(command) + for _, ext := range pathExt { + if strEqual(cmdExt, ext) { + searchExt = false + break + } + } + isExecutable = func(fi os.FileInfo) bool { + return fi.Mode().IsRegular() + } + } else { + isExecutable = func(fi os.FileInfo) bool { + return fi.Mode().IsRegular() && fi.Mode().Perm()&0111 != 0 + } + } + + pathEnv, _ := s.LookupEnv(pathEnvName()) + for _, dir := range strings.Split(pathEnv, string(filepath.ListSeparator)) { + if dir == "" { + continue + } + + // Determine whether dir needs a trailing path separator. + // Note: we avoid filepath.Join in this function because it cleans the + // result: we want to preserve the exact dir prefix from the environment. + sep := string(filepath.Separator) + if os.IsPathSeparator(dir[len(dir)-1]) { + sep = "" + } + + if searchExt { + ents, err := os.ReadDir(dir) + if err != nil { + continue + } + for _, ent := range ents { + for _, ext := range pathExt { + if !ent.IsDir() && strEqual(ent.Name(), command+ext) { + return dir + sep + ent.Name(), nil + } + } + } + } else { + path := dir + sep + command + if fi, err := os.Stat(path); err == nil && isExecutable(fi) { + return path, nil + } + } + } + return "", &exec.Error{Name: command, Err: exec.ErrNotFound} +} + +// pathEnvName returns the platform-specific variable used by os/exec.LookPath +// to look up executable names (either "PATH" or "path"). +// +// TODO(bcmills): Investigate whether we can instead use PATH uniformly and +// rewrite it to $path when executing subprocesses. +func pathEnvName() string { + switch runtime.GOOS { + case "plan9": + return "path" + default: + return "PATH" + } +} + +// Exists checks that the named file(s) exist. +func Exists() Cmd { + return Command( + CmdUsage{ + Summary: "check that files exist", + Args: "[-readonly] [-exec] file...", + }, + func(s *State, args ...string) (WaitFunc, error) { + var readonly, exec bool + loop: + for len(args) > 0 { + switch args[0] { + case "-readonly": + readonly = true + args = args[1:] + case "-exec": + exec = true + args = args[1:] + default: + break loop + } + } + if len(args) == 0 { + return nil, ErrUsage + } + + for _, file := range args { + file = s.Path(file) + info, err := os.Stat(file) + if err != nil { + return nil, err + } + if readonly && info.Mode()&0222 != 0 { + return nil, fmt.Errorf("%s exists but is writable", file) + } + if exec && runtime.GOOS != "windows" && info.Mode()&0111 == 0 { + return nil, fmt.Errorf("%s exists but is not executable", file) + } + } + + return nil, nil + }) +} + +// Grep checks that file content matches a regexp. +// Like stdout/stderr and unlike Unix grep, it accepts Go regexp syntax. +// +// Grep does not modify the State's stdout or stderr buffers. +// (Its output goes to the script log, not stdout.) +func Grep() Cmd { + return Command( + CmdUsage{ + Summary: "find lines in a file that match a pattern", + Args: matchUsage + " file", + Detail: []string{ + "The command succeeds if at least one match (or the exact count, if given) is found.", + "The -q flag suppresses printing of matches.", + }, + RegexpArgs: firstNonFlag, + }, + func(s *State, args ...string) (WaitFunc, error) { + return nil, match(s, args, "", "grep") + }) +} + +const matchUsage = "[-count=N] [-q] 'pattern'" + +// match implements the Grep, Stdout, and Stderr commands. +func match(s *State, args []string, text, name string) error { + n := 0 + if len(args) >= 1 && strings.HasPrefix(args[0], "-count=") { + var err error + n, err = strconv.Atoi(args[0][len("-count="):]) + if err != nil { + return fmt.Errorf("bad -count=: %v", err) + } + if n < 1 { + return fmt.Errorf("bad -count=: must be at least 1") + } + args = args[1:] + } + quiet := false + if len(args) >= 1 && args[0] == "-q" { + quiet = true + args = args[1:] + } + + isGrep := name == "grep" + + wantArgs := 1 + if len(args) != wantArgs { + return ErrUsage + } + + pattern := `(?m)` + args[0] + re, err := regexp.Compile(pattern) + if err != nil { + return err + } + + if isGrep { + if len(args) == 1 || args[1] == "-" { + text = s.stdout + } else { + name = args[1] // for error messages + data, err := os.ReadFile(s.Path(args[1])) + if err != nil { + return err + } + text = string(data) + } + } + + if n > 0 { + count := len(re.FindAllString(text, -1)) + if count != n { + return fmt.Errorf("found %d matches for %#q in %s", count, pattern, name) + } + return nil + } + + if !re.MatchString(text) { + return fmt.Errorf("no match for %#q in %s", pattern, name) + } + + if !quiet { + // Print the lines containing the match. + loc := re.FindStringIndex(text) + for loc[0] > 0 && text[loc[0]-1] != '\n' { + loc[0]-- + } + for loc[1] < len(text) && text[loc[1]] != '\n' { + loc[1]++ + } + lines := strings.TrimSuffix(text[loc[0]:loc[1]], "\n") + s.Logf("matched: %s\n", lines) + } + return nil +} + +// Help writes command documentation to the script log. +func Help() Cmd { + return Command( + CmdUsage{ + Summary: "log help text for commands and conditions", + Args: "[-v] name...", + Detail: []string{ + "To display help for a specific condition, enclose it in brackets: 'help [amd64]'.", + "To display complete documentation when listing all commands, pass the -v flag.", + }, + }, + func(s *State, args ...string) (WaitFunc, error) { + if s.engine == nil { + return nil, errors.New("no engine configured") + } + + verbose := false + if len(args) > 0 { + verbose = true + if args[0] == "-v" { + args = args[1:] + } + } + + var cmds, conds []string + for _, arg := range args { + if strings.HasPrefix(arg, "[") && strings.HasSuffix(arg, "]") { + conds = append(conds, arg[1:len(arg)-1]) + } else { + cmds = append(cmds, arg) + } + } + + out := new(strings.Builder) + + if len(conds) > 0 || (len(args) == 0 && len(s.engine.Conds) > 0) { + if conds == nil { + out.WriteString("conditions:\n\n") + } + s.engine.ListConds(out, s, conds...) + } + + if len(cmds) > 0 || len(args) == 0 { + if len(args) == 0 { + out.WriteString("\ncommands:\n\n") + } + s.engine.ListCmds(out, verbose, cmds...) + } + + wait := func(*State) (stdout, stderr string, err error) { + return out.String(), "", nil + } + return wait, nil + }) +} + +// Mkdir creates a directory and any needed parent directories. +func Mkdir() Cmd { + return Command( + CmdUsage{ + Summary: "create directories, if they do not already exist", + Args: "path...", + Detail: []string{ + "Unlike Unix mkdir, parent directories are always created if needed.", + }, + }, + func(s *State, args ...string) (WaitFunc, error) { + if len(args) < 1 { + return nil, ErrUsage + } + for _, arg := range args { + if err := os.MkdirAll(s.Path(arg), 0777); err != nil { + return nil, err + } + } + return nil, nil + }) +} + +// Mv renames an existing file or directory to a new path. +func Mv() Cmd { + return Command( + CmdUsage{ + Summary: "rename a file or directory to a new path", + Args: "old new", + Detail: []string{ + "OS-specific restrictions may apply when old and new are in different directories.", + }, + }, + func(s *State, args ...string) (WaitFunc, error) { + if len(args) != 2 { + return nil, ErrUsage + } + return nil, os.Rename(s.Path(args[0]), s.Path(args[1])) + }) +} + +// Program returns a new command that runs the named program, found from the +// host process's PATH (not looked up in the script's PATH). +func Program(name string, cancel func(*exec.Cmd) error, waitDelay time.Duration) Cmd { + var ( + shortName string + summary string + lookPathOnce sync.Once + path string + pathErr error + ) + if filepath.IsAbs(name) { + lookPathOnce.Do(func() { path = filepath.Clean(name) }) + shortName = strings.TrimSuffix(filepath.Base(path), ".exe") + summary = "run the '" + shortName + "' program provided by the script host" + } else { + shortName = name + summary = "run the '" + shortName + "' program from the script host's PATH" + } + + return Command( + CmdUsage{ + Summary: summary, + Args: "[args...]", + Async: true, + }, + func(s *State, args ...string) (WaitFunc, error) { + lookPathOnce.Do(func() { + path, pathErr = exec.LookPath(name) + }) + if pathErr != nil { + return nil, pathErr + } + return startCommand(s, shortName, path, args, cancel, waitDelay) + }) +} + +// Replace replaces all occurrences of a string in a file with another string. +func Replace() Cmd { + return Command( + CmdUsage{ + Summary: "replace strings in a file", + Args: "[old new]... file", + Detail: []string{ + "The 'old' and 'new' arguments are unquoted as if in quoted Go strings.", + }, + }, + func(s *State, args ...string) (WaitFunc, error) { + if len(args)%2 != 1 { + return nil, ErrUsage + } + + oldNew := make([]string, 0, len(args)-1) + for _, arg := range args[:len(args)-1] { + s, err := strconv.Unquote(`"` + arg + `"`) + if err != nil { + return nil, err + } + oldNew = append(oldNew, s) + } + + r := strings.NewReplacer(oldNew...) + file := s.Path(args[len(args)-1]) + + data, err := os.ReadFile(file) + if err != nil { + return nil, err + } + replaced := r.Replace(string(data)) + + return nil, os.WriteFile(file, []byte(replaced), 0666) + }) +} + +// Rm removes a file or directory. +// +// If a directory, Rm also recursively removes that directory's +// contents. +func Rm() Cmd { + return Command( + CmdUsage{ + Summary: "remove a file or directory", + Args: "path...", + Detail: []string{ + "If the path is a directory, its contents are removed recursively.", + }, + }, + func(s *State, args ...string) (WaitFunc, error) { + if len(args) < 1 { + return nil, ErrUsage + } + for _, arg := range args { + if err := removeAll(s.Path(arg)); err != nil { + return nil, err + } + } + return nil, nil + }) +} + +// removeAll removes dir and all files and directories it contains. +// +// Unlike os.RemoveAll, removeAll attempts to make the directories writable if +// needed in order to remove their contents. +func removeAll(dir string) error { + // module cache has 0444 directories; + // make them writable in order to remove content. + filepath.WalkDir(dir, func(path string, info fs.DirEntry, err error) error { + // chmod not only directories, but also things that we couldn't even stat + // due to permission errors: they may also be unreadable directories. + if err != nil || info.IsDir() { + os.Chmod(path, 0777) + } + return nil + }) + return os.RemoveAll(dir) +} + +// Sleep sleeps for the given Go duration or until the script's context is +// cancelled, whichever happens first. +func Sleep() Cmd { + return Command( + CmdUsage{ + Summary: "sleep for a specified duration", + Args: "duration", + Detail: []string{ + "The duration must be given as a Go time.Duration string.", + }, + Async: true, + }, + func(s *State, args ...string) (WaitFunc, error) { + if len(args) != 1 { + return nil, ErrUsage + } + + d, err := time.ParseDuration(args[0]) + if err != nil { + return nil, err + } + + timer := time.NewTimer(d) + wait := func(s *State) (stdout, stderr string, err error) { + ctx := s.Context() + select { + case <-ctx.Done(): + timer.Stop() + return "", "", ctx.Err() + case <-timer.C: + return "", "", nil + } + } + return wait, nil + }) +} + +// Stderr searches for a regular expression in the stderr buffer. +func Stderr() Cmd { + return Command( + CmdUsage{ + Summary: "find lines in the stderr buffer that match a pattern", + Args: matchUsage + " file", + Detail: []string{ + "The command succeeds if at least one match (or the exact count, if given) is found.", + "The -q flag suppresses printing of matches.", + }, + RegexpArgs: firstNonFlag, + }, + func(s *State, args ...string) (WaitFunc, error) { + return nil, match(s, args, s.Stderr(), "stderr") + }) +} + +// Stdout searches for a regular expression in the stdout buffer. +func Stdout() Cmd { + return Command( + CmdUsage{ + Summary: "find lines in the stdout buffer that match a pattern", + Args: matchUsage + " file", + Detail: []string{ + "The command succeeds if at least one match (or the exact count, if given) is found.", + "The -q flag suppresses printing of matches.", + }, + RegexpArgs: firstNonFlag, + }, + func(s *State, args ...string) (WaitFunc, error) { + return nil, match(s, args, s.Stdout(), "stdout") + }) +} + +// Stop returns a sentinel error that causes script execution to halt +// and s.Execute to return with a nil error. +func Stop() Cmd { + return Command( + CmdUsage{ + Summary: "stop execution of the script", + Args: "[msg]", + Detail: []string{ + "The message is written to the script log, but no error is reported from the script engine.", + }, + }, + func(s *State, args ...string) (WaitFunc, error) { + if len(args) > 1 { + return nil, ErrUsage + } + // TODO(bcmills): The argument passed to stop seems redundant with comments. + // Either use it systematically or remove it. + if len(args) == 1 { + return nil, stopError{msg: args[0]} + } + return nil, stopError{} + }) +} + +// stopError is the sentinel error type returned by the Stop command. +type stopError struct { + msg string +} + +func (s stopError) Error() string { + if s.msg == "" { + return "stop" + } + return "stop: " + s.msg +} + +// Symlink creates a symbolic link. +func Symlink() Cmd { + return Command( + CmdUsage{ + Summary: "create a symlink", + Args: "path -> target", + Detail: []string{ + "Creates path as a symlink to target.", + "The '->' token (like in 'ls -l' output on Unix) is required.", + }, + }, + func(s *State, args ...string) (WaitFunc, error) { + if len(args) != 3 || args[1] != "->" { + return nil, ErrUsage + } + + // Note that the link target args[2] is not interpreted with s.Path: + // it will be interpreted relative to the directory file is in. + return nil, os.Symlink(filepath.FromSlash(args[2]), s.Path(args[0])) + }) +} + +// Wait waits for the completion of background commands. +// +// When Wait returns, the stdout and stderr buffers contain the concatenation of +// the background commands' respective outputs in the order in which those +// commands were started. +func Wait() Cmd { + return Command( + CmdUsage{ + Summary: "wait for completion of background commands", + Args: "", + Detail: []string{ + "Waits for all background commands to complete.", + "The output (and any error) from each command is printed to the log in the order in which the commands were started.", + "After the call to 'wait', the script's stdout and stderr buffers contain the concatenation of the background commands' outputs.", + }, + }, + func(s *State, args ...string) (WaitFunc, error) { + if len(args) > 0 { + return nil, ErrUsage + } + + var stdouts, stderrs []string + var errs []*CommandError + for _, bg := range s.background { + stdout, stderr, err := bg.wait(s) + + beforeArgs := "" + if len(bg.args) > 0 { + beforeArgs = " " + } + s.Logf("[background] %s%s%s\n", bg.name, beforeArgs, quoteArgs(bg.args)) + + if stdout != "" { + s.Logf("[stdout]\n%s", stdout) + stdouts = append(stdouts, stdout) + } + if stderr != "" { + s.Logf("[stderr]\n%s", stderr) + stderrs = append(stderrs, stderr) + } + if err != nil { + s.Logf("[%v]\n", err) + } + if cmdErr := checkStatus(bg.command, err); cmdErr != nil { + errs = append(errs, cmdErr.(*CommandError)) + } + } + + s.stdout = strings.Join(stdouts, "") + s.stderr = strings.Join(stderrs, "") + s.background = nil + if len(errs) > 0 { + return nil, waitError{errs: errs} + } + return nil, nil + }) +} + +// A waitError wraps one or more errors returned by background commands. +type waitError struct { + errs []*CommandError +} + +func (w waitError) Error() string { + b := new(strings.Builder) + for i, err := range w.errs { + if i != 0 { + b.WriteString("\n") + } + b.WriteString(err.Error()) + } + return b.String() +} + +func (w waitError) Unwrap() error { + if len(w.errs) == 1 { + return w.errs[0] + } + return nil +} + +func Break() Cmd { + return Command( + CmdUsage{ + Summary: "break into interactive prompt", + }, + func(s *State, args ...string) (WaitFunc, error) { + tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) + if err != nil { + return nil, fmt.Errorf("open /dev/tty: %w", err) + } + defer tty.Close() + + prev, err := term.MakeRaw(int(tty.Fd())) + if err != nil { + return nil, fmt.Errorf("cannot set /dev/tty to raw mode") + } + defer term.Restore(int(tty.Fd()), prev) + + // Flush any pending logs + engine := s.engine + + term := term.NewTerminal(tty, "debug> ") + s.FlushLog() + fmt.Fprintf(term, "\nBreak! Control-d to continue.\n") + + for { + line, err := term.ReadLine() + if err != nil { + return nil, nil + } + err = engine.ExecuteLine(s, line, term) + if err != nil { + fmt.Fprintln(term, err.Error()) + } + } + }, + ) +} diff --git a/vendor/github.com/cilium/hive/script/cmds_other.go b/vendor/github.com/cilium/hive/script/cmds_other.go new file mode 100644 index 0000000000..847b225ae6 --- /dev/null +++ b/vendor/github.com/cilium/hive/script/cmds_other.go @@ -0,0 +1,11 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !(unix || windows) + +package script + +func isETXTBSY(err error) bool { + return false +} diff --git a/vendor/github.com/cilium/hive/script/cmds_posix.go b/vendor/github.com/cilium/hive/script/cmds_posix.go new file mode 100644 index 0000000000..2525f6e752 --- /dev/null +++ b/vendor/github.com/cilium/hive/script/cmds_posix.go @@ -0,0 +1,16 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unix || windows + +package script + +import ( + "errors" + "syscall" +) + +func isETXTBSY(err error) bool { + return errors.Is(err, syscall.ETXTBSY) +} diff --git a/vendor/github.com/cilium/hive/script/conds.go b/vendor/github.com/cilium/hive/script/conds.go new file mode 100644 index 0000000000..ffe5e3f0db --- /dev/null +++ b/vendor/github.com/cilium/hive/script/conds.go @@ -0,0 +1,198 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package script + +import ( + "fmt" + "os" + "runtime" + "sync" +) + +// DefaultConds returns a set of broadly useful script conditions. +// +// Run the 'help' command within a script engine to view a list of the available +// conditions. +func DefaultConds() map[string]Cond { + conds := make(map[string]Cond) + + conds["GOOS"] = PrefixCondition( + "runtime.GOOS == ", + func(_ *State, suffix string) (bool, error) { + if suffix == runtime.GOOS { + return true, nil + } + return false, nil + }) + + conds["GOARCH"] = PrefixCondition( + "runtime.GOARCH == ", + func(_ *State, suffix string) (bool, error) { + if suffix == runtime.GOARCH { + return true, nil + } + return false, nil + }) + + conds["compiler"] = PrefixCondition( + "runtime.Compiler == ", + func(_ *State, suffix string) (bool, error) { + if suffix == runtime.Compiler { + return true, nil + } + switch suffix { + case "gc", "gccgo": + return false, nil + default: + return false, fmt.Errorf("unrecognized compiler %q", suffix) + } + }) + + conds["root"] = BoolCondition("os.Geteuid() == 0", os.Geteuid() == 0) + + return conds +} + +// Condition returns a Cond with the given summary and evaluation function. +func Condition(summary string, eval func(*State) (bool, error)) Cond { + return &funcCond{eval: eval, usage: CondUsage{Summary: summary}} +} + +type funcCond struct { + eval func(*State) (bool, error) + usage CondUsage +} + +func (c *funcCond) Usage() *CondUsage { return &c.usage } + +func (c *funcCond) Eval(s *State, suffix string) (bool, error) { + if suffix != "" { + return false, ErrUsage + } + return c.eval(s) +} + +// PrefixCondition returns a Cond with the given summary and evaluation function. +func PrefixCondition(summary string, eval func(*State, string) (bool, error)) Cond { + return &prefixCond{eval: eval, usage: CondUsage{Summary: summary, Prefix: true}} +} + +type prefixCond struct { + eval func(*State, string) (bool, error) + usage CondUsage +} + +func (c *prefixCond) Usage() *CondUsage { return &c.usage } + +func (c *prefixCond) Eval(s *State, suffix string) (bool, error) { + return c.eval(s, suffix) +} + +// BoolCondition returns a Cond with the given truth value and summary. +// The Cond rejects the use of condition suffixes. +func BoolCondition(summary string, v bool) Cond { + return &boolCond{v: v, usage: CondUsage{Summary: summary}} +} + +type boolCond struct { + v bool + usage CondUsage +} + +func (b *boolCond) Usage() *CondUsage { return &b.usage } + +func (b *boolCond) Eval(s *State, suffix string) (bool, error) { + if suffix != "" { + return false, ErrUsage + } + return b.v, nil +} + +// OnceCondition returns a Cond that calls eval the first time the condition is +// evaluated. Future calls reuse the same result. +// +// The eval function is not passed a *State because the condition is cached +// across all execution states and must not vary by state. +func OnceCondition(summary string, eval func() (bool, error)) Cond { + return &onceCond{eval: eval, usage: CondUsage{Summary: summary}} +} + +type onceCond struct { + once sync.Once + v bool + err error + eval func() (bool, error) + usage CondUsage +} + +func (l *onceCond) Usage() *CondUsage { return &l.usage } + +func (l *onceCond) Eval(s *State, suffix string) (bool, error) { + if suffix != "" { + return false, ErrUsage + } + l.once.Do(func() { l.v, l.err = l.eval() }) + return l.v, l.err +} + +// CachedCondition is like Condition but only calls eval the first time the +// condition is evaluated for a given suffix. +// Future calls with the same suffix reuse the earlier result. +// +// The eval function is not passed a *State because the condition is cached +// across all execution states and must not vary by state. +func CachedCondition(summary string, eval func(string) (bool, error)) Cond { + return &cachedCond{eval: eval, usage: CondUsage{Summary: summary, Prefix: true}} +} + +type cachedCond struct { + m sync.Map + eval func(string) (bool, error) + usage CondUsage +} + +func (c *cachedCond) Usage() *CondUsage { return &c.usage } + +func (c *cachedCond) Eval(_ *State, suffix string) (bool, error) { + for { + var ready chan struct{} + + v, loaded := c.m.Load(suffix) + if !loaded { + ready = make(chan struct{}) + v, loaded = c.m.LoadOrStore(suffix, (<-chan struct{})(ready)) + + if !loaded { + inPanic := true + defer func() { + if inPanic { + c.m.Delete(suffix) + } + close(ready) + }() + + b, err := c.eval(suffix) + inPanic = false + + if err == nil { + c.m.Store(suffix, b) + return b, nil + } else { + c.m.Store(suffix, err) + return false, err + } + } + } + + switch v := v.(type) { + case bool: + return v, nil + case error: + return false, v + case <-chan struct{}: + <-v + } + } +} diff --git a/vendor/github.com/cilium/hive/script/engine.go b/vendor/github.com/cilium/hive/script/engine.go new file mode 100644 index 0000000000..7b9b9bf3f8 --- /dev/null +++ b/vendor/github.com/cilium/hive/script/engine.go @@ -0,0 +1,853 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package script implements a small, customizable, platform-agnostic scripting +// language. +// +// Scripts are run by an [Engine] configured with a set of available commands +// and conditions that guard those commands. Each script has an associated +// working directory and environment, along with a buffer containing the stdout +// and stderr output of a prior command, tracked in a [State] that commands can +// inspect and modify. +// +// The default commands configured by [NewEngine] resemble a simplified Unix +// shell. +// +// # Script Language +// +// Each line of a script is parsed into a sequence of space-separated command +// words, with environment variable expansion within each word and # marking an +// end-of-line comment. Additional variables named ':' and '/' are expanded +// within script arguments (expanding to the value of os.PathListSeparator and +// os.PathSeparator respectively) but are not inherited in subprocess +// environments. +// +// Adding single quotes around text keeps spaces in that text from being treated +// as word separators and also disables environment variable expansion. +// Inside a single-quoted block of text, a repeated single quote indicates +// a literal single quote, as in: +// +// 'Don''t communicate by sharing memory.' +// +// A line beginning with # is a comment and conventionally explains what is +// being done or tested at the start of a new section of the script. +// +// Commands are executed one at a time, and errors are checked for each command; +// if any command fails unexpectedly, no subsequent commands in the script are +// executed. The command prefix ! indicates that the command on the rest of the +// line (typically go or a matching predicate) must fail instead of succeeding. +// The command prefix ? indicates that the command may or may not succeed, but +// the script should continue regardless. +// +// The command prefix [cond] indicates that the command on the rest of the line +// should only run when the condition is satisfied. +// +// A condition can be negated: [!root] means to run the rest of the line only if +// the user is not root. Multiple conditions may be given for a single command, +// for example, '[linux] [amd64] skip'. The command will run if all conditions +// are satisfied. +// +// Package script is particularly good for writing tests. +// Ironically, it has no tests. +package script + +import ( + "bufio" + "context" + "errors" + "fmt" + "io" + "sort" + "strings" + "time" +) + +// An Engine stores the configuration for executing a set of scripts. +// +// The same Engine may execute multiple scripts concurrently. +type Engine struct { + Cmds map[string]Cmd + Conds map[string]Cond + + // If Quiet is true, Execute deletes log prints from the previous + // section when starting a new section. + Quiet bool +} + +// NewEngine returns an Engine configured with a basic set of commands and conditions. +func NewEngine() *Engine { + return &Engine{ + Cmds: DefaultCmds(), + Conds: DefaultConds(), + } +} + +// A Cmd is a command that is available to a script. +type Cmd interface { + // Run begins running the command. + // + // If the command produces output or can be run in the background, run returns + // a WaitFunc that will be called to obtain the result of the command and + // update the engine's stdout and stderr buffers. + // + // Run itself and the returned WaitFunc may inspect and/or modify the State, + // but the State's methods must not be called concurrently after Run has + // returned. + // + // Run may retain and access the args slice until the WaitFunc has returned. + Run(s *State, args ...string) (WaitFunc, error) + + // Usage returns the usage for the command, which the caller must not modify. + Usage() *CmdUsage +} + +// A WaitFunc is a function called to retrieve the results of a Cmd. +type WaitFunc func(*State) (stdout, stderr string, err error) + +// A CmdUsage describes the usage of a Cmd, independent of its name +// (which can change based on its registration). +type CmdUsage struct { + Summary string // in the style of the Name section of a Unix 'man' page, omitting the name + Args string // a brief synopsis of the command's arguments (only) + Detail []string // zero or more sentences in the style of the Description section of a Unix 'man' page + + // If Async is true, the Cmd is meaningful to run in the background, and its + // Run method must return either a non-nil WaitFunc or a non-nil error. + Async bool + + // RegexpArgs reports which arguments, if any, should be treated as regular + // expressions. It takes as input the raw, unexpanded arguments and returns + // the list of argument indices that will be interpreted as regular + // expressions. + // + // If RegexpArgs is nil, all arguments are assumed not to be regular + // expressions. + RegexpArgs func(rawArgs ...string) []int +} + +// A Cond is a condition deciding whether a command should be run. +type Cond interface { + // Eval reports whether the condition applies to the given State. + // + // If the condition's usage reports that it is a prefix, + // the condition must be used with a suffix. + // Otherwise, the passed-in suffix argument is always the empty string. + Eval(s *State, suffix string) (bool, error) + + // Usage returns the usage for the condition, which the caller must not modify. + Usage() *CondUsage +} + +// A CondUsage describes the usage of a Cond, independent of its name +// (which can change based on its registration). +type CondUsage struct { + Summary string // a single-line summary of when the condition is true + + // If Prefix is true, the condition is a prefix and requires a + // colon-separated suffix (like "[GOOS:linux]" for the "GOOS" condition). + // The suffix may be the empty string (like "[prefix:]"). + Prefix bool +} + +// Execute reads and executes script, writing the output to log. +// +// Execute stops and returns an error at the first command that does not succeed. +// The returned error's text begins with "file:line: ". +// +// If the script runs to completion or ends by a 'stop' command, +// Execute returns nil. +// +// Execute does not stop background commands started by the script +// before returning. To stop those, use [State.CloseAndWait] or the +// [Wait] command. +func (e *Engine) Execute(s *State, file string, script *bufio.Reader, log io.Writer) (err error) { + defer func(prev *Engine) { s.engine = prev }(s.engine) + s.engine = e + defer func(prev io.Writer) { s.logOut = prev }(s.logOut) + s.logOut = log + + var sectionStart time.Time + // endSection flushes the logs for the current section from s.log to log. + // ok indicates whether all commands in the section succeeded. + endSection := func(ok bool) error { + var err error + if sectionStart.IsZero() { + // We didn't write a section header or record a timestamp, so just dump the + // whole log without those. + if s.log.Len() > 0 { + err = s.FlushLog() + } + } else if s.log.Len() == 0 { + // Adding elapsed time for doing nothing is meaningless, so don't. + _, err = io.WriteString(log, "\n") + } else { + // Insert elapsed time for section at the end of the section's comment. + _, err = fmt.Fprintf(log, " (%.3fs)\n", time.Since(sectionStart).Seconds()) + + if err == nil && (!ok || !e.Quiet) { + err = s.FlushLog() + } else { + s.log.Reset() + } + } + + sectionStart = time.Time{} + return err + } + + var lineno int + lineErr := func(err error) error { + if errors.As(err, new(*CommandError)) { + return err + } + return fmt.Errorf("%s:%d: %w", file, lineno, err) + } + + // In case of failure or panic, flush any pending logs for the section. + defer func() { + if sErr := endSection(false); sErr != nil && err == nil { + err = lineErr(sErr) + } + }() + + for { + if err := s.ctx.Err(); err != nil { + // This error wasn't produced by any particular command, + // so don't wrap it in a CommandError. + return lineErr(err) + } + + line, err := script.ReadString('\n') + if err == io.EOF { + if line == "" { + break // Reached the end of the script. + } + // If the script doesn't end in a newline, interpret the final line. + } else if err != nil { + return lineErr(err) + } + line = strings.TrimSuffix(line, "\n") + lineno++ + + // The comment character "#" at the start of the line delimits a section of + // the script. + if strings.HasPrefix(line, "#") { + // If there was a previous section, the fact that we are starting a new + // one implies the success of the previous one. + // + // At the start of the script, the state may also contain accumulated logs + // from commands executed on the State outside of the engine in order to + // set it up; flush those logs too. + if err := endSection(true); err != nil { + return lineErr(err) + } + + // Log the section start without a newline so that we can add + // a timestamp for the section when it ends. + _, err = fmt.Fprintf(log, "%s", line) + sectionStart = time.Now() + if err != nil { + return lineErr(err) + } + continue + } + + cmd, err := parse(file, lineno, line) + if cmd == nil && err == nil { + continue // Ignore blank lines. + } + s.Logf("> %s\n", line) + if err != nil { + return lineErr(err) + } + + // Evaluate condition guards. + ok, err := e.conditionsActive(s, cmd.conds) + if err != nil { + return lineErr(err) + } + if !ok { + s.Logf("[condition not met]\n") + continue + } + + impl := e.Cmds[cmd.name] + + // Expand variables in arguments. + var regexpArgs []int + if impl != nil { + usage := impl.Usage() + if usage.RegexpArgs != nil { + // First join rawArgs without expansion to pass to RegexpArgs. + rawArgs := make([]string, 0, len(cmd.rawArgs)) + for _, frags := range cmd.rawArgs { + var b strings.Builder + for _, frag := range frags { + b.WriteString(frag.s) + } + rawArgs = append(rawArgs, b.String()) + } + regexpArgs = usage.RegexpArgs(rawArgs...) + } + } + cmd.args = expandArgs(s, cmd.rawArgs, regexpArgs) + + // Run the command. + err = e.runCommand(s, cmd, impl) + if err != nil { + if stop := (stopError{}); errors.As(err, &stop) { + // Since the 'stop' command halts execution of the entire script, + // log its message separately from the section in which it appears. + err = endSection(true) + s.Logf("%v\n", stop) + if err == nil { + return nil + } + } + return lineErr(err) + } + } + + if err := endSection(true); err != nil { + return lineErr(err) + } + return nil +} + +func (e *Engine) ExecuteLine(s *State, line string, log io.Writer) (err error) { + defer func(prev *Engine) { s.engine = prev }(s.engine) + s.engine = e + defer func(prev io.Writer) { s.logOut = prev }(s.logOut) + s.logOut = log + defer s.FlushLog() + + cmd, err := parse("", 0, line) + if cmd == nil && err == nil { + return nil + } + if err != nil { + return err + } + + // Evaluate condition guards. + ok, err := e.conditionsActive(s, cmd.conds) + if err != nil { + return err + } + if !ok { + s.Logf("[condition not met]\n") + return + } + + impl := e.Cmds[cmd.name] + + // Expand variables in arguments. + var regexpArgs []int + if impl != nil { + usage := impl.Usage() + if usage.RegexpArgs != nil { + // First join rawArgs without expansion to pass to RegexpArgs. + rawArgs := make([]string, 0, len(cmd.rawArgs)) + for _, frags := range cmd.rawArgs { + var b strings.Builder + for _, frag := range frags { + b.WriteString(frag.s) + } + rawArgs = append(rawArgs, b.String()) + } + regexpArgs = usage.RegexpArgs(rawArgs...) + } + } + cmd.args = expandArgs(s, cmd.rawArgs, regexpArgs) + + // Run the command. + err = e.runCommand(s, cmd, impl) + if err != nil { + if stop := (stopError{}); errors.As(err, &stop) { + // Since the 'stop' command halts execution of the entire script, + // log its message separately from the section in which it appears. + s.Logf("%v\n", stop) + if err == nil { + return nil + } + } + return err + } + return nil +} + +// A command is a complete command parsed from a script. +type command struct { + file string + line int + want expectedStatus + conds []condition // all must be satisfied + name string // the name of the command; must be non-empty + rawArgs [][]argFragment + args []string // shell-expanded arguments following name + background bool // command should run in background (ends with a trailing &) +} + +// An expectedStatus describes the expected outcome of a command. +// Script execution halts when a command does not match its expected status. +type expectedStatus string + +const ( + success expectedStatus = "" + failure expectedStatus = "!" + successOrFailure expectedStatus = "?" +) + +type argFragment struct { + s string + quoted bool // if true, disable variable expansion for this fragment +} + +type condition struct { + want bool + tag string +} + +const argSepChars = " \t\r\n#" + +// parse parses a single line as a list of space-separated arguments. +// subject to environment variable expansion (but not resplitting). +// Single quotes around text disable splitting and expansion. +// To embed a single quote, double it: +// +// 'Don''t communicate by sharing memory.' +func parse(filename string, lineno int, line string) (cmd *command, err error) { + cmd = &command{file: filename, line: lineno} + var ( + rawArg []argFragment // text fragments of current arg so far (need to add line[start:i]) + start = -1 // if >= 0, position where current arg text chunk starts + quoted = false // currently processing quoted text + ) + + flushArg := func() error { + if len(rawArg) == 0 { + return nil // Nothing to flush. + } + defer func() { rawArg = nil }() + + if cmd.name == "" && len(rawArg) == 1 && !rawArg[0].quoted { + arg := rawArg[0].s + + // Command prefix ! means negate the expectations about this command: + // go command should fail, match should not be found, etc. + // Prefix ? means allow either success or failure. + switch want := expectedStatus(arg); want { + case failure, successOrFailure: + if cmd.want != "" { + return errors.New("duplicated '!' or '?' token") + } + cmd.want = want + return nil + } + + // Command prefix [cond] means only run this command if cond is satisfied. + if strings.HasPrefix(arg, "[") && strings.HasSuffix(arg, "]") { + want := true + arg = strings.TrimSpace(arg[1 : len(arg)-1]) + if strings.HasPrefix(arg, "!") { + want = false + arg = strings.TrimSpace(arg[1:]) + } + if arg == "" { + return errors.New("empty condition") + } + cmd.conds = append(cmd.conds, condition{want: want, tag: arg}) + return nil + } + + if arg == "" { + return errors.New("empty command") + } + cmd.name = arg + return nil + } + + cmd.rawArgs = append(cmd.rawArgs, rawArg) + return nil + } + + for i := 0; ; i++ { + if !quoted && (i >= len(line) || strings.ContainsRune(argSepChars, rune(line[i]))) { + // Found arg-separating space. + if start >= 0 { + rawArg = append(rawArg, argFragment{s: line[start:i], quoted: false}) + start = -1 + } + if err := flushArg(); err != nil { + return nil, err + } + if i >= len(line) || line[i] == '#' { + break + } + continue + } + if i >= len(line) { + return nil, errors.New("unterminated quoted argument") + } + if line[i] == '\'' { + if !quoted { + // starting a quoted chunk + if start >= 0 { + rawArg = append(rawArg, argFragment{s: line[start:i], quoted: false}) + } + start = i + 1 + quoted = true + continue + } + // 'foo''bar' means foo'bar, like in rc shell and Pascal. + if i+1 < len(line) && line[i+1] == '\'' { + rawArg = append(rawArg, argFragment{s: line[start:i], quoted: true}) + start = i + 1 + i++ // skip over second ' before next iteration + continue + } + // ending a quoted chunk + rawArg = append(rawArg, argFragment{s: line[start:i], quoted: true}) + start = i + 1 + quoted = false + continue + } + // found character worth saving; make sure we're saving + if start < 0 { + start = i + } + } + + if cmd.name == "" { + if cmd.want != "" || len(cmd.conds) > 0 || len(cmd.rawArgs) > 0 || cmd.background { + // The line contains a command prefix or suffix, but no actual command. + return nil, errors.New("missing command") + } + + // The line is blank, or contains only a comment. + return nil, nil + } + + if n := len(cmd.rawArgs); n > 0 { + last := cmd.rawArgs[n-1] + if len(last) == 1 && !last[0].quoted && last[0].s == "&" { + cmd.background = true + cmd.rawArgs = cmd.rawArgs[:n-1] + } + } + return cmd, nil +} + +// expandArgs expands the shell variables in rawArgs and joins them to form the +// final arguments to pass to a command. +func expandArgs(s *State, rawArgs [][]argFragment, regexpArgs []int) []string { + args := make([]string, 0, len(rawArgs)) + for i, frags := range rawArgs { + isRegexp := false + for _, j := range regexpArgs { + if i == j { + isRegexp = true + break + } + } + + var b strings.Builder + for _, frag := range frags { + if frag.quoted { + b.WriteString(frag.s) + } else { + b.WriteString(s.ExpandEnv(frag.s, isRegexp)) + } + } + args = append(args, b.String()) + } + return args +} + +// quoteArgs returns a string that parse would parse as args when passed to a command. +// +// TODO(bcmills): This function should have a fuzz test. +func quoteArgs(args []string) string { + var b strings.Builder + for i, arg := range args { + if i > 0 { + b.WriteString(" ") + } + if strings.ContainsAny(arg, "'"+argSepChars) { + // Quote the argument to a form that would be parsed as a single argument. + b.WriteString("'") + b.WriteString(strings.ReplaceAll(arg, "'", "''")) + b.WriteString("'") + } else { + b.WriteString(arg) + } + } + return b.String() +} + +func (e *Engine) conditionsActive(s *State, conds []condition) (bool, error) { + for _, cond := range conds { + var impl Cond + prefix, suffix, ok := strings.Cut(cond.tag, ":") + if ok { + impl = e.Conds[prefix] + if impl == nil { + return false, fmt.Errorf("unknown condition prefix %q", prefix) + } + if !impl.Usage().Prefix { + return false, fmt.Errorf("condition %q cannot be used with a suffix", prefix) + } + } else { + impl = e.Conds[cond.tag] + if impl == nil { + return false, fmt.Errorf("unknown condition %q", cond.tag) + } + if impl.Usage().Prefix { + return false, fmt.Errorf("condition %q requires a suffix", cond.tag) + } + } + active, err := impl.Eval(s, suffix) + + if err != nil { + return false, fmt.Errorf("evaluating condition %q: %w", cond.tag, err) + } + if active != cond.want { + return false, nil + } + } + + return true, nil +} + +func (e *Engine) runCommand(s *State, cmd *command, impl Cmd) error { + if impl == nil { + return cmdError(cmd, errors.New("unknown command")) + } + + async := impl.Usage().Async + if cmd.background && !async { + return cmdError(cmd, errors.New("command cannot be run in background")) + } + + wait, runErr := impl.Run(s, cmd.args...) + if wait == nil { + if async && runErr == nil { + return cmdError(cmd, errors.New("internal error: async command returned a nil WaitFunc")) + } + return checkStatus(cmd, runErr) + } + if runErr != nil { + return cmdError(cmd, errors.New("internal error: command returned both an error and a WaitFunc")) + } + + if cmd.background { + s.background = append(s.background, backgroundCmd{ + command: cmd, + wait: wait, + }) + // Clear stdout and stderr, since they no longer correspond to the last + // command executed. + s.stdout = "" + s.stderr = "" + return nil + } + + stdout, stderr, waitErr := wait(s) + s.stdout = stdout + s.stderr = stderr + if stdout != "" { + s.Logf("[stdout]\n%s", stdout) + } + if stderr != "" { + s.Logf("[stderr]\n%s", stderr) + } + if cmdErr := checkStatus(cmd, waitErr); cmdErr != nil { + return cmdErr + } + if waitErr != nil { + // waitErr was expected (by cmd.want), so log it instead of returning it. + s.Logf("[%v]\n", waitErr) + } + return nil +} + +func checkStatus(cmd *command, err error) error { + if err == nil { + if cmd.want == failure { + return cmdError(cmd, ErrUnexpectedSuccess) + } + return nil + } + + if s := (stopError{}); errors.As(err, &s) { + // This error originated in the Stop command. + // Propagate it as-is. + return cmdError(cmd, err) + } + + if w := (waitError{}); errors.As(err, &w) { + // This error was surfaced from a background process by a call to Wait. + // Add a call frame for Wait itself, but ignore its "want" field. + // (Wait itself cannot fail to wait on commands or else it would leak + // processes and/or goroutines — so a negative assertion for it would be at + // best ambiguous.) + return cmdError(cmd, err) + } + + if cmd.want == success { + return cmdError(cmd, err) + } + + if cmd.want == failure && (errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled)) { + // The command was terminated because the script is no longer interested in + // its output, so we don't know what it would have done had it run to + // completion — for all we know, it could have exited without error if it + // ran just a smidge faster. + return cmdError(cmd, err) + } + + return nil +} + +// ListCmds prints to w a list of the named commands, +// annotating each with its arguments and a short usage summary. +// If verbose is true, ListCmds prints full details for each command. +// +// Each of the name arguments should be a command name. +// If no names are passed as arguments, ListCmds lists all the +// commands registered in e. +func (e *Engine) ListCmds(w io.Writer, verbose bool, names ...string) error { + if names == nil { + names = make([]string, 0, len(e.Cmds)) + for name := range e.Cmds { + names = append(names, name) + } + sort.Strings(names) + } + + for _, name := range names { + cmd := e.Cmds[name] + usage := cmd.Usage() + + suffix := "" + if usage.Async { + suffix = " [&]" + } + + _, err := fmt.Fprintf(w, "%s %s%s\n\t%s\n", name, usage.Args, suffix, usage.Summary) + if err != nil { + return err + } + + if verbose { + if _, err := io.WriteString(w, "\n"); err != nil { + return err + } + for _, line := range usage.Detail { + if err := wrapLine(w, line, 60, "\t"); err != nil { + return err + } + } + if _, err := io.WriteString(w, "\n"); err != nil { + return err + } + } + } + + return nil +} + +func wrapLine(w io.Writer, line string, cols int, indent string) error { + line = strings.TrimLeft(line, " ") + for len(line) > cols { + bestSpace := -1 + for i, r := range line { + if r == ' ' { + if i <= cols || bestSpace < 0 { + bestSpace = i + } + if i > cols { + break + } + } + } + if bestSpace < 0 { + break + } + + if _, err := fmt.Fprintf(w, "%s%s\n", indent, line[:bestSpace]); err != nil { + return err + } + line = line[bestSpace+1:] + } + + _, err := fmt.Fprintf(w, "%s%s\n", indent, line) + return err +} + +// ListConds prints to w a list of conditions, one per line, +// annotating each with a description and whether the condition +// is true in the state s (if s is non-nil). +// +// Each of the tag arguments should be a condition string of +// the form "name" or "name:suffix". If no tags are passed as +// arguments, ListConds lists all conditions registered in +// the engine e. +func (e *Engine) ListConds(w io.Writer, s *State, tags ...string) error { + if tags == nil { + tags = make([]string, 0, len(e.Conds)) + for name := range e.Conds { + tags = append(tags, name) + } + sort.Strings(tags) + } + + for _, tag := range tags { + if prefix, suffix, ok := strings.Cut(tag, ":"); ok { + cond := e.Conds[prefix] + if cond == nil { + return fmt.Errorf("unknown condition prefix %q", prefix) + } + usage := cond.Usage() + if !usage.Prefix { + return fmt.Errorf("condition %q cannot be used with a suffix", prefix) + } + + activeStr := "" + if s != nil { + if active, _ := cond.Eval(s, suffix); active { + activeStr = " (active)" + } + } + _, err := fmt.Fprintf(w, "[%s]%s\n\t%s\n", tag, activeStr, usage.Summary) + if err != nil { + return err + } + continue + } + + cond := e.Conds[tag] + if cond == nil { + return fmt.Errorf("unknown condition %q", tag) + } + var err error + usage := cond.Usage() + if usage.Prefix { + _, err = fmt.Fprintf(w, "[%s:*]\n\t%s\n", tag, usage.Summary) + } else { + activeStr := "" + if s != nil { + if ok, _ := cond.Eval(s, ""); ok { + activeStr = " (active)" + } + } + _, err = fmt.Fprintf(w, "[%s]%s\n\t%s\n", tag, activeStr, usage.Summary) + } + if err != nil { + return err + } + } + + return nil +} diff --git a/vendor/github.com/cilium/hive/script/errors.go b/vendor/github.com/cilium/hive/script/errors.go new file mode 100644 index 0000000000..7f43e72888 --- /dev/null +++ b/vendor/github.com/cilium/hive/script/errors.go @@ -0,0 +1,64 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package script + +import ( + "errors" + "fmt" +) + +// ErrUnexpectedSuccess indicates that a script command that was expected to +// fail (as indicated by a "!" prefix) instead completed successfully. +var ErrUnexpectedSuccess = errors.New("unexpected success") + +// A CommandError describes an error resulting from attempting to execute a +// specific command. +type CommandError struct { + File string + Line int + Op string + Args []string + Err error +} + +func cmdError(cmd *command, err error) *CommandError { + return &CommandError{ + File: cmd.file, + Line: cmd.line, + Op: cmd.name, + Args: cmd.args, + Err: err, + } +} + +func (e *CommandError) Error() string { + if len(e.Args) == 0 { + return fmt.Sprintf("%s:%d: %s: %v", e.File, e.Line, e.Op, e.Err) + } + return fmt.Sprintf("%s:%d: %s %s: %v", e.File, e.Line, e.Op, quoteArgs(e.Args), e.Err) +} + +func (e *CommandError) Unwrap() error { return e.Err } + +// A UsageError reports the valid arguments for a command. +// +// It may be returned in response to invalid arguments. +type UsageError struct { + Name string + Command Cmd +} + +func (e *UsageError) Error() string { + usage := e.Command.Usage() + suffix := "" + if usage.Async { + suffix = " [&]" + } + return fmt.Sprintf("usage: %s %s%s", e.Name, usage.Args, suffix) +} + +// ErrUsage may be returned by a Command to indicate that it was called with +// invalid arguments; its Usage method may be called to obtain details. +var ErrUsage = errors.New("invalid usage") diff --git a/vendor/github.com/cilium/hive/script/internal/diff/diff.go b/vendor/github.com/cilium/hive/script/internal/diff/diff.go new file mode 100644 index 0000000000..0aeeb75eb0 --- /dev/null +++ b/vendor/github.com/cilium/hive/script/internal/diff/diff.go @@ -0,0 +1,261 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package diff + +import ( + "bytes" + "fmt" + "sort" + "strings" +) + +// A pair is a pair of values tracked for both the x and y side of a diff. +// It is typically a pair of line indexes. +type pair struct{ x, y int } + +// Diff returns an anchored diff of the two texts old and new +// in the “unified diff” format. If old and new are identical, +// Diff returns a nil slice (no output). +// +// Unix diff implementations typically look for a diff with +// the smallest number of lines inserted and removed, +// which can in the worst case take time quadratic in the +// number of lines in the texts. As a result, many implementations +// either can be made to run for a long time or cut off the search +// after a predetermined amount of work. +// +// In contrast, this implementation looks for a diff with the +// smallest number of “unique” lines inserted and removed, +// where unique means a line that appears just once in both old and new. +// We call this an “anchored diff” because the unique lines anchor +// the chosen matching regions. An anchored diff is usually clearer +// than a standard diff, because the algorithm does not try to +// reuse unrelated blank lines or closing braces. +// The algorithm also guarantees to run in O(n log n) time +// instead of the standard O(n²) time. +// +// Some systems call this approach a “patience diff,” named for +// the “patience sorting” algorithm, itself named for a solitaire card game. +// We avoid that name for two reasons. First, the name has been used +// for a few different variants of the algorithm, so it is imprecise. +// Second, the name is frequently interpreted as meaning that you have +// to wait longer (to be patient) for the diff, meaning that it is a slower algorithm, +// when in fact the algorithm is faster than the standard one. +func Diff(oldName string, old []byte, newName string, new []byte) []byte { + if bytes.Equal(old, new) { + return nil + } + x := lines(old) + y := lines(new) + + // Print diff header. + var out bytes.Buffer + fmt.Fprintf(&out, "diff %s %s\n", oldName, newName) + fmt.Fprintf(&out, "--- %s\n", oldName) + fmt.Fprintf(&out, "+++ %s\n", newName) + + // Loop over matches to consider, + // expanding each match to include surrounding lines, + // and then printing diff chunks. + // To avoid setup/teardown cases outside the loop, + // tgs returns a leading {0,0} and trailing {len(x), len(y)} pair + // in the sequence of matches. + var ( + done pair // printed up to x[:done.x] and y[:done.y] + chunk pair // start lines of current chunk + count pair // number of lines from each side in current chunk + ctext []string // lines for current chunk + ) + for _, m := range tgs(x, y) { + if m.x < done.x { + // Already handled scanning forward from earlier match. + continue + } + + // Expand matching lines as far possible, + // establishing that x[start.x:end.x] == y[start.y:end.y]. + // Note that on the first (or last) iteration we may (or definitely do) + // have an empty match: start.x==end.x and start.y==end.y. + start := m + for start.x > done.x && start.y > done.y && x[start.x-1] == y[start.y-1] { + start.x-- + start.y-- + } + end := m + for end.x < len(x) && end.y < len(y) && x[end.x] == y[end.y] { + end.x++ + end.y++ + } + + // Emit the mismatched lines before start into this chunk. + // (No effect on first sentinel iteration, when start = {0,0}.) + for _, s := range x[done.x:start.x] { + ctext = append(ctext, "-"+s) + count.x++ + } + for _, s := range y[done.y:start.y] { + ctext = append(ctext, "+"+s) + count.y++ + } + + // If we're not at EOF and have too few common lines, + // the chunk includes all the common lines and continues. + const C = 3 // number of context lines + if (end.x < len(x) || end.y < len(y)) && + (end.x-start.x < C || (len(ctext) > 0 && end.x-start.x < 2*C)) { + for _, s := range x[start.x:end.x] { + ctext = append(ctext, " "+s) + count.x++ + count.y++ + } + done = end + continue + } + + // End chunk with common lines for context. + if len(ctext) > 0 { + n := end.x - start.x + if n > C { + n = C + } + for _, s := range x[start.x : start.x+n] { + ctext = append(ctext, " "+s) + count.x++ + count.y++ + } + done = pair{start.x + n, start.y + n} + + // Format and emit chunk. + // Convert line numbers to 1-indexed. + // Special case: empty file shows up as 0,0 not 1,0. + if count.x > 0 { + chunk.x++ + } + if count.y > 0 { + chunk.y++ + } + fmt.Fprintf(&out, "@@ -%d,%d +%d,%d @@\n", chunk.x, count.x, chunk.y, count.y) + for _, s := range ctext { + out.WriteString(s) + } + count.x = 0 + count.y = 0 + ctext = ctext[:0] + } + + // If we reached EOF, we're done. + if end.x >= len(x) && end.y >= len(y) { + break + } + + // Otherwise start a new chunk. + chunk = pair{end.x - C, end.y - C} + for _, s := range x[chunk.x:end.x] { + ctext = append(ctext, " "+s) + count.x++ + count.y++ + } + done = end + } + + return out.Bytes() +} + +// lines returns the lines in the file x, including newlines. +// If the file does not end in a newline, one is supplied +// along with a warning about the missing newline. +func lines(x []byte) []string { + l := strings.SplitAfter(string(x), "\n") + if l[len(l)-1] == "" { + l = l[:len(l)-1] + } else { + // Treat last line as having a message about the missing newline attached, + // using the same text as BSD/GNU diff (including the leading backslash). + l[len(l)-1] += "\n\\ No newline at end of file\n" + } + return l +} + +// tgs returns the pairs of indexes of the longest common subsequence +// of unique lines in x and y, where a unique line is one that appears +// once in x and once in y. +// +// The longest common subsequence algorithm is as described in +// Thomas G. Szymanski, “A Special Case of the Maximal Common +// Subsequence Problem,” Princeton TR #170 (January 1975), +// available at https://research.swtch.com/tgs170.pdf. +func tgs(x, y []string) []pair { + // Count the number of times each string appears in a and b. + // We only care about 0, 1, many, counted as 0, -1, -2 + // for the x side and 0, -4, -8 for the y side. + // Using negative numbers now lets us distinguish positive line numbers later. + m := make(map[string]int) + for _, s := range x { + if c := m[s]; c > -2 { + m[s] = c - 1 + } + } + for _, s := range y { + if c := m[s]; c > -8 { + m[s] = c - 4 + } + } + + // Now unique strings can be identified by m[s] = -1+-4. + // + // Gather the indexes of those strings in x and y, building: + // xi[i] = increasing indexes of unique strings in x. + // yi[i] = increasing indexes of unique strings in y. + // inv[i] = index j such that x[xi[i]] = y[yi[j]]. + var xi, yi, inv []int + for i, s := range y { + if m[s] == -1+-4 { + m[s] = len(yi) + yi = append(yi, i) + } + } + for i, s := range x { + if j, ok := m[s]; ok && j >= 0 { + xi = append(xi, i) + inv = append(inv, j) + } + } + + // Apply Algorithm A from Szymanski's paper. + // In those terms, A = J = inv and B = [0, n). + // We add sentinel pairs {0,0}, and {len(x),len(y)} + // to the returned sequence, to help the processing loop. + J := inv + n := len(xi) + T := make([]int, n) + L := make([]int, n) + for i := range T { + T[i] = n + 1 + } + for i := 0; i < n; i++ { + k := sort.Search(n, func(k int) bool { + return T[k] >= J[i] + }) + T[k] = J[i] + L[i] = k + 1 + } + k := 0 + for _, v := range L { + if k < v { + k = v + } + } + seq := make([]pair, 2+k) + seq[1+k] = pair{len(x), len(y)} // sentinel at end + lastj := n + for i := n - 1; i >= 0; i-- { + if L[i] == k && J[i] < lastj { + seq[k] = pair{xi[i], yi[J[i]]} + k-- + } + } + seq[0] = pair{0, 0} // sentinel at start + return seq +} diff --git a/vendor/github.com/cilium/hive/script/makeraw_unix.go b/vendor/github.com/cilium/hive/script/makeraw_unix.go new file mode 100644 index 0000000000..3fffbc74dc --- /dev/null +++ b/vendor/github.com/cilium/hive/script/makeraw_unix.go @@ -0,0 +1,37 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build darwin || dragonfly || freebsd || netbsd || openbsd || aix || linux || solaris || zos + +package script + +import ( + "golang.org/x/sys/unix" +) + +// MakeRaw sets the terminal to raw mode, but with interrupt signals enabled. +func MakeRaw(fd int) (restore func(), err error) { + termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios) + if err != nil { + return nil, err + } + + oldState := *termios + + termios.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON + termios.Oflag &^= unix.OPOST + termios.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.IEXTEN + termios.Lflag |= unix.ISIG // Enable interrupt signals + termios.Cflag &^= unix.CSIZE | unix.PARENB + termios.Cflag |= unix.CS8 + termios.Cc[unix.VMIN] = 1 + termios.Cc[unix.VTIME] = 0 + if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, termios); err != nil { + return nil, err + } + + return func() { + unix.IoctlSetTermios(fd, ioctlWriteTermios, &oldState) + }, nil +} diff --git a/vendor/github.com/cilium/hive/script/makeraw_unix_bsd.go b/vendor/github.com/cilium/hive/script/makeraw_unix_bsd.go new file mode 100644 index 0000000000..064368d40a --- /dev/null +++ b/vendor/github.com/cilium/hive/script/makeraw_unix_bsd.go @@ -0,0 +1,16 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build darwin || dragonfly || freebsd || netbsd || openbsd + +package script + +import ( + "golang.org/x/sys/unix" +) + +const ( + ioctlReadTermios = unix.TIOCGETA + ioctlWriteTermios = unix.TIOCSETA +) diff --git a/vendor/github.com/cilium/hive/script/makeraw_unix_other.go b/vendor/github.com/cilium/hive/script/makeraw_unix_other.go new file mode 100644 index 0000000000..84449a5f56 --- /dev/null +++ b/vendor/github.com/cilium/hive/script/makeraw_unix_other.go @@ -0,0 +1,14 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build aix || linux || solaris || zos + +package script + +import "golang.org/x/sys/unix" + +const ( + ioctlReadTermios = unix.TCGETS + ioctlWriteTermios = unix.TCSETS +) diff --git a/vendor/github.com/cilium/hive/script/makeraw_unsupported.go b/vendor/github.com/cilium/hive/script/makeraw_unsupported.go new file mode 100644 index 0000000000..fe88ed87b2 --- /dev/null +++ b/vendor/github.com/cilium/hive/script/makeraw_unsupported.go @@ -0,0 +1,16 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !darwin && !linux + +package script + +import ( + "fmt" + "runtime" +) + +func MakeRaw(fd int) (restore func(), err error) { + return func() {}, fmt.Errorf("MakeRaw: not supported on %s", runtime.GOOS) +} diff --git a/vendor/github.com/cilium/hive/script/state.go b/vendor/github.com/cilium/hive/script/state.go new file mode 100644 index 0000000000..1226dcf60e --- /dev/null +++ b/vendor/github.com/cilium/hive/script/state.go @@ -0,0 +1,244 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package script + +import ( + "bytes" + "context" + "fmt" + "io" + "io/fs" + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" + + "golang.org/x/tools/txtar" +) + +// A State encapsulates the current state of a running script engine, +// including the script environment and any running background commands. +type State struct { + engine *Engine // the engine currently executing the script, if any + + ctx context.Context + cancel context.CancelFunc + file string + log bytes.Buffer + logOut io.Writer + + workdir string // initial working directory + pwd string // current working directory during execution + env []string // environment list (for os/exec) + envMap map[string]string // environment mapping (matches env) + stdout string // standard output from last 'go' command; for 'stdout' command + stderr string // standard error from last 'go' command; for 'stderr' command + + background []backgroundCmd +} + +type backgroundCmd struct { + *command + wait WaitFunc +} + +// NewState returns a new State permanently associated with ctx, with its +// initial working directory in workdir and its initial environment set to +// initialEnv (or os.Environ(), if initialEnv is nil). +// +// The new State also contains pseudo-environment-variables for +// ${/} and ${:} (for the platform's path and list separators respectively), +// but does not pass those to subprocesses. +func NewState(ctx context.Context, workdir string, initialEnv []string) (*State, error) { + absWork, err := filepath.Abs(workdir) + if err != nil { + return nil, err + } + + ctx, cancel := context.WithCancel(ctx) + + // Make a fresh copy of the env slice to avoid aliasing bugs if we ever + // start modifying it in place; this also establishes the invariant that + // s.env contains no duplicates. + env := cleanEnv(initialEnv, absWork) + + envMap := make(map[string]string, len(env)) + + // Add entries for ${:} and ${/} to make it easier to write platform-independent + // paths in scripts. + envMap["/"] = string(os.PathSeparator) + envMap[":"] = string(os.PathListSeparator) + + for _, kv := range env { + if k, v, ok := strings.Cut(kv, "="); ok { + envMap[k] = v + } + } + + s := &State{ + ctx: ctx, + cancel: cancel, + workdir: absWork, + pwd: absWork, + env: env, + envMap: envMap, + } + s.Setenv("PWD", absWork) + return s, nil +} + +// CloseAndWait cancels the State's Context and waits for any background commands to +// finish. If any remaining background command ended in an unexpected state, +// Close returns a non-nil error. +func (s *State) CloseAndWait(log io.Writer) error { + s.cancel() + wait, err := Wait().Run(s) + if wait != nil { + panic("script: internal error: Wait unexpectedly returns its own WaitFunc") + } + defer func(prev io.Writer) { s.logOut = prev }(s.logOut) + s.logOut = log + if flushErr := s.FlushLog(); err == nil { + err = flushErr + } + return err +} + +// Chdir changes the State's working directory to the given path. +func (s *State) Chdir(path string) error { + dir := s.Path(path) + if _, err := os.Stat(dir); err != nil { + return &fs.PathError{Op: "Chdir", Path: dir, Err: err} + } + s.pwd = dir + s.Setenv("PWD", dir) + return nil +} + +// Context returns the Context with which the State was created. +func (s *State) Context() context.Context { + return s.ctx +} + +// Environ returns a copy of the current script environment, +// in the form "key=value". +func (s *State) Environ() []string { + return append([]string(nil), s.env...) +} + +// ExpandEnv replaces ${var} or $var in the string according to the values of +// the environment variables in s. References to undefined variables are +// replaced by the empty string. +func (s *State) ExpandEnv(str string, inRegexp bool) string { + return os.Expand(str, func(key string) string { + e := s.envMap[key] + if inRegexp { + // Quote to literal strings: we want paths like C:\work\go1.4 to remain + // paths rather than regular expressions. + e = regexp.QuoteMeta(e) + } + return e + }) +} + +// ExtractFiles extracts the files in ar to the state's current directory, +// expanding any environment variables within each name. +// +// The files must reside within the working directory with which the State was +// originally created. +func (s *State) ExtractFiles(ar *txtar.Archive) error { + wd := s.workdir + + // Add trailing separator to terminate wd. + // This prevents extracting to outside paths which prefix wd, + // e.g. extracting to /home/foobar when wd is /home/foo + if wd == "" { + panic("s.workdir is unexpectedly empty") + } + if !os.IsPathSeparator(wd[len(wd)-1]) { + wd += string(filepath.Separator) + } + + for _, f := range ar.Files { + name := s.Path(s.ExpandEnv(f.Name, false)) + + if !strings.HasPrefix(name, wd) { + return fmt.Errorf("file %#q is outside working directory", f.Name) + } + + if err := os.MkdirAll(filepath.Dir(name), 0777); err != nil { + return err + } + if err := os.WriteFile(name, f.Data, 0666); err != nil { + return err + } + } + + return nil +} + +// Getwd returns the directory in which to run the next script command. +func (s *State) Getwd() string { return s.pwd } + +// Logf writes output to the script's log without updating its stdout or stderr +// buffers. (The output log functions as a kind of meta-stderr.) +func (s *State) Logf(format string, args ...any) { + fmt.Fprintf(&s.log, format, args...) +} + +func (s *State) LogWriter() io.Writer { + return &s.log +} + +// FlushLog writes out the contents of the script's log and clears the buffer. +func (s *State) FlushLog() error { + _, err := s.logOut.Write(s.log.Bytes()) + s.log.Reset() + return err +} + +// LookupEnv retrieves the value of the environment variable in s named by the key. +func (s *State) LookupEnv(key string) (string, bool) { + v, ok := s.envMap[key] + return v, ok +} + +// Path returns the absolute path in the host operating system for a +// script-based (generally slash-separated and relative) path. +func (s *State) Path(path string) string { + if filepath.IsAbs(path) { + return filepath.Clean(path) + } + return filepath.Join(s.pwd, path) +} + +// Setenv sets the value of the environment variable in s named by the key. +func (s *State) Setenv(key, value string) error { + s.env = cleanEnv(append(s.env, key+"="+value), s.pwd) + s.envMap[key] = value + return nil +} + +// Stdout returns the stdout output of the last command run, +// or the empty string if no command has been run. +func (s *State) Stdout() string { return s.stdout } + +// Stderr returns the stderr output of the last command run, +// or the empty string if no command has been run. +func (s *State) Stderr() string { return s.stderr } + +// cleanEnv returns a copy of env with any duplicates removed in favor of +// later values and any required system variables defined. +// +// If env is nil, cleanEnv copies the environment from os.Environ(). +func cleanEnv(env []string, pwd string) []string { + // There are some funky edge-cases in this logic, especially on Windows (with + // case-insensitive environment variables and variables with keys like "=C:"). + // Rather than duplicating exec.dedupEnv here, cheat and use exec.Cmd directly. + cmd := &exec.Cmd{Env: env} + cmd.Dir = pwd + return cmd.Environ() +} diff --git a/vendor/github.com/cilium/statedb/any_table.go b/vendor/github.com/cilium/statedb/any_table.go new file mode 100644 index 0000000000..ebea155734 --- /dev/null +++ b/vendor/github.com/cilium/statedb/any_table.go @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Cilium + +package statedb + +import ( + "fmt" + "iter" +) + +// AnyTable allows any-typed access to a StateDB table. This is intended +// for building generic tooling for accessing the table and should be +// avoided if possible. +type AnyTable struct { + Meta TableMeta +} + +func (t AnyTable) All(txn ReadTxn) iter.Seq2[any, Revision] { + all, _ := t.AllWatch(txn) + return all +} + +func (t AnyTable) AllWatch(txn ReadTxn) (iter.Seq2[any, Revision], <-chan struct{}) { + indexTxn := txn.getTxn().mustIndexReadTxn(t.Meta, PrimaryIndexPos) + return partSeq[any](indexTxn.Iterator()), indexTxn.RootWatch() +} + +func (t AnyTable) UnmarshalYAML(data []byte) (any, error) { + return t.Meta.unmarshalYAML(data) +} + +func (t AnyTable) Insert(txn WriteTxn, obj any) (old any, hadOld bool, err error) { + var iobj object + iobj, hadOld, err = txn.getTxn().insert(t.Meta, Revision(0), obj) + if hadOld { + old = iobj.data + } + return +} + +func (t AnyTable) Delete(txn WriteTxn, obj any) (old any, hadOld bool, err error) { + var iobj object + iobj, hadOld, err = txn.getTxn().delete(t.Meta, Revision(0), obj) + if hadOld { + old = iobj.data + } + return +} + +func (t AnyTable) Get(txn ReadTxn, index string, key string) (any, Revision, bool, error) { + itxn, rawKey, err := t.queryIndex(txn, index, key) + if err != nil { + return nil, 0, false, err + } + if itxn.unique { + obj, _, ok := itxn.Get(rawKey) + return obj.data, obj.revision, ok, nil + } + // For non-unique indexes we need to prefix search and make sure to fully + // match the secondary key. + iter, _ := itxn.Prefix(rawKey) + for { + k, obj, ok := iter.Next() + if !ok { + break + } + secondary, _ := decodeNonUniqueKey(k) + if len(secondary) == len(rawKey) { + return obj.data, obj.revision, true, nil + } + } + return nil, 0, false, nil +} + +func (t AnyTable) Prefix(txn ReadTxn, index string, key string) (iter.Seq2[any, Revision], error) { + itxn, rawKey, err := t.queryIndex(txn, index, key) + if err != nil { + return nil, err + } + iter, _ := itxn.Prefix(rawKey) + if itxn.unique { + return partSeq[any](iter), nil + } + return nonUniqueSeq[any](iter, true, rawKey), nil +} + +func (t AnyTable) LowerBound(txn ReadTxn, index string, key string) (iter.Seq2[any, Revision], error) { + itxn, rawKey, err := t.queryIndex(txn, index, key) + if err != nil { + return nil, err + } + iter := itxn.LowerBound(rawKey) + if itxn.unique { + return partSeq[any](iter), nil + } + return nonUniqueLowerBoundSeq[any](iter, rawKey), nil +} + +func (t AnyTable) List(txn ReadTxn, index string, key string) (iter.Seq2[any, Revision], error) { + itxn, rawKey, err := t.queryIndex(txn, index, key) + if err != nil { + return nil, err + } + iter, _ := itxn.Prefix(rawKey) + if itxn.unique { + // Unique index means that there can be only a single matching object. + // Doing a Get() is more efficient than constructing an iterator. + value, _, ok := itxn.Get(rawKey) + return func(yield func(any, Revision) bool) { + if ok { + yield(value.data, value.revision) + } + }, nil + } + return nonUniqueSeq[any](iter, false, rawKey), nil +} + +func (t AnyTable) queryIndex(txn ReadTxn, index string, key string) (indexReadTxn, []byte, error) { + indexer := t.Meta.getIndexer(index) + if indexer == nil { + return indexReadTxn{}, nil, fmt.Errorf("invalid index %q", index) + } + rawKey, err := indexer.fromString(key) + if err != nil { + return indexReadTxn{}, nil, err + } + itxn, err := txn.getTxn().indexReadTxn(t.Meta, indexer.pos) + return itxn, rawKey, err +} + +func (t AnyTable) Changes(txn WriteTxn) (anyChangeIterator, error) { + return t.Meta.anyChanges(txn) +} + +func (t AnyTable) TableHeader() []string { + zero := t.Meta.proto() + if tw, ok := zero.(TableWritable); ok { + return tw.TableHeader() + } + return nil +} + +func (t AnyTable) Proto() any { + return t.Meta.proto() +} diff --git a/vendor/github.com/cilium/statedb/cell.go b/vendor/github.com/cilium/statedb/cell.go index 38b0658777..9a23395074 100644 --- a/vendor/github.com/cilium/statedb/cell.go +++ b/vendor/github.com/cilium/statedb/cell.go @@ -16,6 +16,7 @@ var Cell = cell.Module( cell.Provide( newHiveDB, + ScriptCommands, ), ) diff --git a/vendor/github.com/cilium/statedb/db.go b/vendor/github.com/cilium/statedb/db.go index 2ec4d62a93..be6f18ec41 100644 --- a/vendor/github.com/cilium/statedb/db.go +++ b/vendor/github.com/cilium/statedb/db.go @@ -208,9 +208,20 @@ func (db *DB) WriteTxn(table TableMeta, tables ...TableMeta) WriteTxn { lockAt := time.Now() smus.Lock() acquiredAt := time.Now() - root := *db.root.Load() tableEntries := make([]*tableEntry, len(root)) + + txn := &txn{ + db: db, + root: root, + handle: db.handleName, + acquiredAt: time.Now(), + writeTxn: writeTxn{ + modifiedTables: tableEntries, + smus: smus, + }, + } + var tableNames []string for _, table := range allTables { tableEntry := root[table.tablePos()] @@ -223,10 +234,12 @@ func (db *DB) WriteTxn(table TableMeta, tables ...TableMeta) WriteTxn { table.Name(), table.sortableMutex().AcquireDuration(), ) + table.acquired(txn) } // Sort the table names so they always appear ordered in metrics. sort.Strings(tableNames) + txn.tableNames = tableNames db.metrics.WriteTxnTotalAcquisition( db.handleName, @@ -234,19 +247,29 @@ func (db *DB) WriteTxn(table TableMeta, tables ...TableMeta) WriteTxn { acquiredAt.Sub(lockAt), ) - txn := &txn{ - db: db, - root: root, - modifiedTables: tableEntries, - smus: smus, - acquiredAt: acquiredAt, - tableNames: tableNames, - handle: db.handleName, - } runtime.SetFinalizer(txn, txnFinalizer) return txn } +func (db *DB) GetTables(txn ReadTxn) (tbls []TableMeta) { + root := txn.getTxn().root + tbls = make([]TableMeta, 0, len(root)) + for _, table := range root { + tbls = append(tbls, table.meta) + } + return +} + +func (db *DB) GetTable(txn ReadTxn, name string) TableMeta { + root := txn.getTxn().root + for _, table := range root { + if table.meta.Name() == name { + return table.meta + } + } + return nil +} + // Start the background workers for the database. // // This starts the graveyard worker that deals with garbage collecting diff --git a/vendor/github.com/cilium/statedb/http.go b/vendor/github.com/cilium/statedb/http.go index 37b01019c5..96b1a651fd 100644 --- a/vendor/github.com/cilium/statedb/http.go +++ b/vendor/github.com/cilium/statedb/http.go @@ -137,7 +137,7 @@ func runQuery(indexTxn indexReadTxn, lowerbound bool, queryKey []byte, onObject match = func(k []byte) bool { return len(k) == len(queryKey) } default: match = func(k []byte) bool { - _, secondary := decodeNonUniqueKey(k) + secondary, _ := decodeNonUniqueKey(k) return len(secondary) == len(queryKey) } } diff --git a/vendor/github.com/cilium/statedb/index/bool.go b/vendor/github.com/cilium/statedb/index/bool.go index c60f7b9e61..8e37371542 100644 --- a/vendor/github.com/cilium/statedb/index/bool.go +++ b/vendor/github.com/cilium/statedb/index/bool.go @@ -3,6 +3,8 @@ package index +import "strconv" + var ( trueKey = []byte{'T'} falseKey = []byte{'F'} @@ -14,3 +16,8 @@ func Bool(b bool) Key { } return falseKey } + +func BoolString(s string) (Key, error) { + b, err := strconv.ParseBool(s) + return Bool(b), err +} diff --git a/vendor/github.com/cilium/statedb/index/int.go b/vendor/github.com/cilium/statedb/index/int.go index 0b285c2638..caf26d8a88 100644 --- a/vendor/github.com/cilium/statedb/index/int.go +++ b/vendor/github.com/cilium/statedb/index/int.go @@ -5,6 +5,7 @@ package index import ( "encoding/binary" + "strconv" ) // The indexing functions on integers should use big-endian encoding. @@ -19,26 +20,78 @@ func Int(n int) Key { return Int32(int32(n)) } +func IntString(s string) (Key, error) { + return Int32String(s) +} + func Int64(n int64) Key { return Uint64(uint64(n)) } +func Int64String(s string) (Key, error) { + n, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return Key{}, err + } + return Uint64(uint64(n)), nil +} + func Int32(n int32) Key { return Uint32(uint32(n)) } +func Int32String(s string) (Key, error) { + n, err := strconv.ParseInt(s, 10, 32) + if err != nil { + return Key{}, err + } + return Uint32(uint32(n)), nil +} + func Int16(n int16) Key { return Uint16(uint16(n)) } +func Int16String(s string) (Key, error) { + n, err := strconv.ParseInt(s, 10, 16) + if err != nil { + return Key{}, err + } + return Uint16(uint16(n)), nil +} + func Uint64(n uint64) Key { return binary.BigEndian.AppendUint64(nil, n) } +func Uint64String(s string) (Key, error) { + n, err := strconv.ParseUint(s, 10, 64) + if err != nil { + return Key{}, err + } + return Uint64(n), nil +} + func Uint32(n uint32) Key { return binary.BigEndian.AppendUint32(nil, n) } +func Uint32String(s string) (Key, error) { + n, err := strconv.ParseUint(s, 10, 32) + if err != nil { + return Key{}, err + } + return Uint32(uint32(n)), nil +} + func Uint16(n uint16) Key { return binary.BigEndian.AppendUint16(nil, n) } + +func Uint16String(s string) (Key, error) { + n, err := strconv.ParseUint(s, 10, 16) + if err != nil { + return Key{}, err + } + return Uint16(uint16(n)), nil +} diff --git a/vendor/github.com/cilium/statedb/index/netip.go b/vendor/github.com/cilium/statedb/index/netip.go index b8223d0554..7a95f63469 100644 --- a/vendor/github.com/cilium/statedb/index/netip.go +++ b/vendor/github.com/cilium/statedb/index/netip.go @@ -20,8 +20,24 @@ func NetIPAddr(addr netip.Addr) Key { return buf[:] } +func NetIPAddrString(s string) (Key, error) { + addr, err := netip.ParseAddr(s) + if err != nil { + return Key{}, err + } + return NetIPAddr(addr), nil +} + func NetIPPrefix(prefix netip.Prefix) Key { // Use the 16-byte form plus bits to have a constant-size key. addrBytes := prefix.Addr().As16() return append(addrBytes[:], uint8(prefix.Bits())) } + +func NetIPPrefixString(s string) (Key, error) { + prefix, err := netip.ParsePrefix(s) + if err != nil { + return Key{}, err + } + return NetIPPrefix(prefix), nil +} diff --git a/vendor/github.com/cilium/statedb/index/string.go b/vendor/github.com/cilium/statedb/index/string.go index 9a678f0ca2..99430bc3bd 100644 --- a/vendor/github.com/cilium/statedb/index/string.go +++ b/vendor/github.com/cilium/statedb/index/string.go @@ -12,6 +12,10 @@ func String(s string) Key { return []byte(s) } +func FromString(s string) (Key, error) { + return String(s), nil +} + func Stringer[T fmt.Stringer](s T) Key { return String(s.String()) } diff --git a/vendor/github.com/cilium/statedb/internal/time.go b/vendor/github.com/cilium/statedb/internal/time.go new file mode 100644 index 0000000000..5463ea0e4e --- /dev/null +++ b/vendor/github.com/cilium/statedb/internal/time.go @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Cilium + +package internal + +import ( + "fmt" + "time" +) + +func PrettySince(t time.Time) string { + return PrettyDuration(time.Since(t)) +} + +func PrettyDuration(d time.Duration) string { + ago := float64(d) / float64(time.Microsecond) + + // micros + if ago < 1000.0 { + return fmt.Sprintf("%.1fus", ago) + } + + // millis + ago /= 1000.0 + if ago < 1000.0 { + return fmt.Sprintf("%.1fms", ago) + } + // secs + ago /= 1000.0 + if ago < 60.0 { + return fmt.Sprintf("%.1fs", ago) + } + // mins + ago /= 60.0 + if ago < 60.0 { + return fmt.Sprintf("%.1fm", ago) + } + // hours + ago /= 60.0 + return fmt.Sprintf("%.1fh", ago) +} diff --git a/vendor/github.com/cilium/statedb/iterator.go b/vendor/github.com/cilium/statedb/iterator.go index 08b4e79042..301ce330d5 100644 --- a/vendor/github.com/cilium/statedb/iterator.go +++ b/vendor/github.com/cilium/statedb/iterator.go @@ -4,6 +4,7 @@ package statedb import ( + "bytes" "fmt" "iter" "slices" @@ -53,7 +54,7 @@ func ToSeq[A, B any](seq iter.Seq2[A, B]) iter.Seq[A] { } } -// partSeq returns a sequence of objects from a part Iterator. +// partSeq returns a casted sequence of objects from a part Iterator. func partSeq[Obj any](iter *part.Iterator[object]) iter.Seq2[Obj, Revision] { return func(yield func(Obj, Revision) bool) { // Iterate over a clone of the original iterator to allow the sequence to be iterated @@ -75,7 +76,7 @@ func partSeq[Obj any](iter *part.Iterator[object]) iter.Seq2[Obj, Revision] { // Non-unique indexes work by concatenating the secondary key with the // primary key and then prefix searching for the items: // -// +// \0 // ^^^^^^^^^^^ // // Since the primary key can be of any length and we're prefix searching, @@ -84,27 +85,53 @@ func partSeq[Obj any](iter *part.Iterator[object]) iter.Seq2[Obj, Revision] { // For example if we search for the key "aaaa", then we might have the following // matches (_ is just delimiting, not part of the key): // -// aaaa_bbb4 -// aaa_abab3 -// aaaa_ccc4 +// aaaa\0bbb4 +// aaa\0abab3 +// aaaa\0ccc4 // -// We yield "aaaa_bbb4", skip "aaa_abab3" and yield "aaaa_ccc4". -func nonUniqueSeq[Obj any](iter *part.Iterator[object], searchKey []byte) iter.Seq2[Obj, Revision] { +// We yield "aaaa\0bbb4", skip "aaa\0abab3" and yield "aaaa\0ccc4". +func nonUniqueSeq[Obj any](iter *part.Iterator[object], prefixSearch bool, searchKey []byte) iter.Seq2[Obj, Revision] { return func(yield func(Obj, Revision) bool) { // Clone the iterator to allow multiple iterations over the sequence. it := iter.Clone() + + var visited map[string]struct{} + if prefixSearch { + // When prefix searching, keep track of objects we've already seen as + // multiple keys in non-unique index may map to a single object. + // When just doing a List() on a non-unique index we will see each object + // only once and do not need to track this. + // + // This of course makes iterating over a non-unique index with a prefix + // (or lowerbound search) about 20x slower than normal! + visited = map[string]struct{}{} + } + for { key, iobj, ok := it.Next() if !ok { break } - _, secondary := decodeNonUniqueKey(key) + secondary, primary := decodeNonUniqueKey(key) - // The secondary key doesn't match the search key. Since the primary - // key length can vary, we need to continue the prefix search. - if len(secondary) != len(searchKey) { + switch { + case !prefixSearch && len(secondary) != len(searchKey): + // This a List(), thus secondary key must match length exactly. continue + case prefixSearch && len(secondary) < len(searchKey): + // This is Prefix(), thus key must be equal or longer to search key. + continue + } + + if prefixSearch { + // When doing a prefix search on a non-unique index we may see the + // same object multiple times since multiple keys may point it. + // Skip if we've already seen this object. + if _, found := visited[string(primary)]; found { + continue + } + visited[string(primary)] = struct{}{} } if !yield(iobj.data.(Obj), iobj.revision) { @@ -114,6 +141,37 @@ func nonUniqueSeq[Obj any](iter *part.Iterator[object], searchKey []byte) iter.S } } +func nonUniqueLowerBoundSeq[Obj any](iter *part.Iterator[object], searchKey []byte) iter.Seq2[Obj, Revision] { + return func(yield func(Obj, Revision) bool) { + // Clone the iterator to allow multiple uses. + iter = iter.Clone() + + // Keep track of objects we've already seen as multiple keys in non-unique + // index may map to a single object. + visited := map[string]struct{}{} + for { + key, iobj, ok := iter.Next() + if !ok { + break + } + // With a non-unique index we have a composite key . + // This means we need to check every key that it's larger or equal to the search key. + // Just seeking to the first one isn't enough as the secondary key length may vary. + secondary, primary := decodeNonUniqueKey(key) + if bytes.Compare(secondary, searchKey) >= 0 { + if _, found := visited[string(primary)]; found { + continue + } + visited[string(primary)] = struct{}{} + + if !yield(iobj.data.(Obj), iobj.revision) { + return + } + } + } + } +} + // iterator adapts the "any" object iterator to a typed object. type iterator[Obj any] struct { iter interface{ Next() ([]byte, object, bool) } @@ -128,6 +186,13 @@ func (it *iterator[Obj]) Next() (obj Obj, revision uint64, ok bool) { return } +// Iterator for iterating a sequence objects. +type Iterator[Obj any] interface { + // Next returns the next object and its revision if ok is true, otherwise + // zero values to mean that the iteration has finished. + Next() (obj Obj, rev Revision, ok bool) +} + func NewDualIterator[Obj any](left, right Iterator[Obj]) *DualIterator[Obj] { return &DualIterator[Obj]{ left: iterState[Obj]{iter: left}, diff --git a/vendor/github.com/cilium/statedb/part/map.go b/vendor/github.com/cilium/statedb/part/map.go index 9548a147a7..70d0b070fa 100644 --- a/vendor/github.com/cilium/statedb/part/map.go +++ b/vendor/github.com/cilium/statedb/part/map.go @@ -9,6 +9,8 @@ import ( "fmt" "iter" "reflect" + + "gopkg.in/yaml.v3" ) // Map of key-value pairs. The zero value is ready for use, provided @@ -22,8 +24,8 @@ type Map[K, V any] struct { } type mapKVPair[K, V any] struct { - Key K `json:"k"` - Value V `json:"v"` + Key K `json:"k" yaml:"k"` + Value V `json:"v" yaml:"v"` } // FromMap copies values from the hash map into the given Map. @@ -238,3 +240,29 @@ func (m *Map[K, V]) UnmarshalJSON(data []byte) error { m.tree = txn.CommitOnly() return nil } + +func (m Map[K, V]) MarshalYAML() (any, error) { + kvs := make([]mapKVPair[K, V], 0, m.Len()) + iter := m.tree.Iterator() + for _, kv, ok := iter.Next(); ok; _, kv, ok = iter.Next() { + kvs = append(kvs, kv) + } + return kvs, nil +} + +func (m *Map[K, V]) UnmarshalYAML(value *yaml.Node) error { + if value.Kind != yaml.SequenceNode { + return fmt.Errorf("%T.UnmarshalYAML: expected sequence", m) + } + m.ensureTree() + txn := m.tree.Txn() + for _, e := range value.Content { + var kv mapKVPair[K, V] + if err := e.Decode(&kv); err != nil { + return err + } + txn.Insert(m.bytesFromKey(kv.Key), mapKVPair[K, V]{kv.Key, kv.Value}) + } + m.tree = txn.CommitOnly() + return nil +} diff --git a/vendor/github.com/cilium/statedb/part/set.go b/vendor/github.com/cilium/statedb/part/set.go index 8c677bcda6..89a91f0a67 100644 --- a/vendor/github.com/cilium/statedb/part/set.go +++ b/vendor/github.com/cilium/statedb/part/set.go @@ -8,6 +8,9 @@ import ( "encoding/json" "fmt" "iter" + "slices" + + "gopkg.in/yaml.v3" ) // Set is a persistent (immutable) set of values. A Set can be @@ -208,6 +211,32 @@ func (s *Set[T]) UnmarshalJSON(data []byte) error { return nil } +func (s Set[T]) MarshalYAML() (any, error) { + // TODO: Once yaml.v3 supports iter.Seq, drop the Collect(). + return slices.Collect(s.All()), nil +} + +func (s *Set[T]) UnmarshalYAML(value *yaml.Node) error { + if value.Kind != yaml.SequenceNode { + return fmt.Errorf("%T.UnmarshalYAML: expected sequence", s) + } + + if s.tree == nil { + *s = NewSet[T]() + } + txn := s.tree.Txn() + + for _, e := range value.Content { + var v T + if err := e.Decode(&v); err != nil { + return err + } + txn.Insert(s.toBytes(v), v) + } + s.tree = txn.CommitOnly() + return nil +} + func toSeq[T any](iter *Iterator[T]) iter.Seq[T] { return func(yield func(T) bool) { if iter == nil { diff --git a/vendor/github.com/cilium/statedb/reconciler/metrics.go b/vendor/github.com/cilium/statedb/reconciler/metrics.go index f30984a8ee..e53b3b1003 100644 --- a/vendor/github.com/cilium/statedb/reconciler/metrics.go +++ b/vendor/github.com/cilium/statedb/reconciler/metrics.go @@ -24,6 +24,8 @@ const ( ) type ExpVarMetrics struct { + root *expvar.Map + ReconciliationCountVar *expvar.Map ReconciliationDurationVar *expvar.Map ReconciliationTotalErrorsVar *expvar.Map @@ -73,14 +75,22 @@ func NewUnpublishedExpVarMetrics() *ExpVarMetrics { return newExpVarMetrics(false) } +func (m *ExpVarMetrics) Map() *expvar.Map { + return m.root +} + func newExpVarMetrics(publish bool) *ExpVarMetrics { + root := new(expvar.Map).Init() newMap := func(name string) *expvar.Map { if publish { return expvar.NewMap(name) } - return new(expvar.Map).Init() + m := new(expvar.Map).Init() + root.Set(name, m) + return m } return &ExpVarMetrics{ + root: root, ReconciliationCountVar: newMap("reconciliation_count"), ReconciliationDurationVar: newMap("reconciliation_duration"), ReconciliationTotalErrorsVar: newMap("reconciliation_total_errors"), diff --git a/vendor/github.com/cilium/statedb/reconciler/types.go b/vendor/github.com/cilium/statedb/reconciler/types.go index aa69beb285..01b9fa8224 100644 --- a/vendor/github.com/cilium/statedb/reconciler/types.go +++ b/vendor/github.com/cilium/statedb/reconciler/types.go @@ -19,6 +19,8 @@ import ( "github.com/cilium/hive/job" "github.com/cilium/statedb" "github.com/cilium/statedb/index" + "github.com/cilium/statedb/internal" + "gopkg.in/yaml.v3" ) type Reconciler[Obj any] interface { @@ -142,36 +144,48 @@ type Status struct { id uint64 } -func (s Status) IsPendingOrRefreshing() bool { - return s.Kind == StatusKindPending || s.Kind == StatusKindRefreshing +// statusJSON defines the JSON/YAML format for [Status]. Separate to +// [Status] to allow custom unmarshalling that fills in [id]. +type statusJSON struct { + Kind string `json:"kind" yaml:"kind"` + UpdatedAt time.Time `json:"updated-at" yaml:"updated-at"` + Error string `json:"error,omitempty" yaml:"error,omitempty"` } -func (s Status) String() string { - if s.Kind == StatusKindError { - return fmt.Sprintf("Error: %s (%s ago)", s.Error, prettySince(s.UpdatedAt)) - } - return fmt.Sprintf("%s (%s ago)", s.Kind, prettySince(s.UpdatedAt)) +func (sj *statusJSON) fill(s *Status) { + s.Kind = StatusKind(sj.Kind) + s.UpdatedAt = sj.UpdatedAt + s.Error = sj.Error + s.id = nextID() } -func prettySince(t time.Time) string { - ago := float64(time.Now().Sub(t)) / float64(time.Millisecond) - // millis - if ago < 1000.0 { - return fmt.Sprintf("%.1fms", ago) +func (s *Status) UnmarshalYAML(value *yaml.Node) error { + var sj statusJSON + if err := value.Decode(&sj); err != nil { + return err } - // secs - ago /= 1000.0 - if ago < 60.0 { - return fmt.Sprintf("%.1fs", ago) + sj.fill(s) + return nil +} + +func (s *Status) UnmarshalJSON(data []byte) error { + var sj statusJSON + if err := json.Unmarshal(data, &sj); err != nil { + return err } - // mins - ago /= 60.0 - if ago < 60.0 { - return fmt.Sprintf("%.1fm", ago) + sj.fill(s) + return nil +} + +func (s Status) IsPendingOrRefreshing() bool { + return s.Kind == StatusKindPending || s.Kind == StatusKindRefreshing +} + +func (s Status) String() string { + if s.Kind == StatusKindError { + return fmt.Sprintf("Error: %s (%s ago)", s.Error, internal.PrettySince(s.UpdatedAt)) } - // hours - ago /= 60.0 - return fmt.Sprintf("%.1fh", ago) + return fmt.Sprintf("%s (%s ago)", s.Kind, internal.PrettySince(s.UpdatedAt)) } var idGen atomic.Uint64 @@ -206,6 +220,7 @@ func StatusRefreshing() Status { Kind: StatusKindRefreshing, UpdatedAt: time.Now(), Error: "", + id: nextID(), } } @@ -216,6 +231,7 @@ func StatusDone() Status { Kind: StatusKindDone, UpdatedAt: time.Now(), Error: "", + id: nextID(), } } @@ -226,6 +242,7 @@ func StatusError(err error) Status { Kind: StatusKindError, UpdatedAt: time.Now(), Error: err.Error(), + id: nextID(), } } @@ -314,7 +331,7 @@ func (s StatusSet) String() string { b.WriteString(strings.Join(done, " ")) } b.WriteString(" (") - b.WriteString(prettySince(updatedAt)) + b.WriteString(internal.PrettySince(updatedAt)) b.WriteString(" ago)") return b.String() } diff --git a/vendor/github.com/cilium/statedb/script.go b/vendor/github.com/cilium/statedb/script.go new file mode 100644 index 0000000000..88488a8c20 --- /dev/null +++ b/vendor/github.com/cilium/statedb/script.go @@ -0,0 +1,836 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Cilium + +package statedb + +import ( + "bytes" + "encoding/json" + "errors" + "flag" + "fmt" + "io" + "iter" + "maps" + "os" + "regexp" + "slices" + "strings" + "time" + + "github.com/cilium/hive" + "github.com/cilium/hive/script" + "github.com/liggitt/tabwriter" + "golang.org/x/time/rate" + "gopkg.in/yaml.v3" +) + +func ScriptCommands(db *DB) hive.ScriptCmdOut { + subCmds := map[string]script.Cmd{ + "tables": TablesCmd(db), + "show": ShowCmd(db), + "cmp": CompareCmd(db), + "insert": InsertCmd(db), + "delete": DeleteCmd(db), + "get": GetCmd(db), + "prefix": PrefixCmd(db), + "list": ListCmd(db), + "lowerbound": LowerBoundCmd(db), + "watch": WatchCmd(db), + "initialized": InitializedCmd(db), + } + subCmdsList := strings.Join(slices.Collect(maps.Keys(subCmds)), ", ") + return hive.NewScriptCmd( + "db", + script.Command( + script.CmdUsage{ + Summary: "Inspect and manipulate StateDB", + Args: "cmd args...", + Detail: []string{ + "Supported commands: " + subCmdsList, + }, + }, + func(s *script.State, args ...string) (script.WaitFunc, error) { + if len(args) < 1 { + return nil, fmt.Errorf("expected command (%s)", subCmdsList) + } + cmd, ok := subCmds[args[0]] + if !ok { + return nil, fmt.Errorf("command not found, expected one of %s", subCmdsList) + } + wf, err := cmd.Run(s, args[1:]...) + if errors.Is(err, errUsage) { + s.Logf("usage: db %s %s\n", args[0], cmd.Usage().Args) + } + return wf, err + }, + ), + ) +} + +var errUsage = errors.New("bad arguments") + +func TablesCmd(db *DB) script.Cmd { + return script.Command( + script.CmdUsage{ + Summary: "Show StateDB tables", + Args: "table", + }, + func(s *script.State, args ...string) (script.WaitFunc, error) { + txn := db.ReadTxn() + tbls := db.GetTables(txn) + w := newTabWriter(s.LogWriter()) + fmt.Fprintf(w, "Name\tObject count\tDeleted objects\tIndexes\tInitializers\tGo type\tLast WriteTxn\n") + for _, tbl := range tbls { + idxs := strings.Join(tbl.Indexes(), ", ") + fmt.Fprintf(w, "%s\t%d\t%d\t%s\t%v\t%T\t%s\n", + tbl.Name(), tbl.NumObjects(txn), tbl.numDeletedObjects(txn), idxs, tbl.PendingInitializers(txn), tbl.proto(), tbl.getAcquiredInfo()) + } + w.Flush() + return nil, nil + }, + ) +} + +func newCmdFlagSet() *flag.FlagSet { + return &flag.FlagSet{ + // Disable showing the normal usage. + Usage: func() {}, + } +} + +func InitializedCmd(db *DB) script.Cmd { + return script.Command( + script.CmdUsage{ + Summary: "Wait until all or specific tables have been initialized", + Args: "(-timeout=) table...", + }, + func(s *script.State, args ...string) (script.WaitFunc, error) { + txn := db.ReadTxn() + allTbls := db.GetTables(txn) + tbls := allTbls + + flags := newCmdFlagSet() + timeout := flags.Duration("timeout", 5*time.Second, "Maximum amount of time to wait for the table contents to match") + if err := flags.Parse(args); err != nil { + return nil, fmt.Errorf("%w: %s", errUsage, err) + } + timeoutChan := time.After(*timeout) + args = flags.Args() + + if len(args) > 0 { + // Specific tables requested, look them up. + tbls = make([]TableMeta, 0, len(args)) + for _, tableName := range args { + found := false + for _, tbl := range allTbls { + if tableName == tbl.Name() { + tbls = append(tbls, tbl) + found = true + break + } + } + if !found { + return nil, fmt.Errorf("table %q not found", tableName) + } + } + } + + for _, tbl := range tbls { + init, watch := tbl.Initialized(txn) + if init { + s.Logf("%s initialized\n", tbl.Name()) + continue + } + s.Logf("Waiting for %s to initialize (%v)...\n", tbl.Name(), tbl.PendingInitializers(txn)) + select { + case <-s.Context().Done(): + return nil, s.Context().Err() + case <-timeoutChan: + return nil, fmt.Errorf("timed out") + case <-watch: + s.Logf("%s initialized\n", tbl.Name()) + } + } + return nil, nil + }, + ) +} + +func ShowCmd(db *DB) script.Cmd { + return script.Command( + script.CmdUsage{ + Summary: "Show table", + Args: "(-o=) (-columns=col1,...) (-format={table,yaml,json}) table", + }, + func(s *script.State, args ...string) (script.WaitFunc, error) { + flags := newCmdFlagSet() + file := flags.String("o", "", "File to write to instead of stdout") + columns := flags.String("columns", "", "Comma-separated list of columns to write") + format := flags.String("format", "table", "Format to write in (table, yaml, json)") + if err := flags.Parse(args); err != nil { + return nil, fmt.Errorf("%w: %s", errUsage, err) + } + + var cols []string + if len(*columns) > 0 { + cols = strings.Split(*columns, ",") + } + + args = flags.Args() + if len(args) < 1 { + return nil, fmt.Errorf("%w: missing table name", errUsage) + } + tableName := args[0] + return func(*script.State) (stdout, stderr string, err error) { + var buf strings.Builder + var w io.Writer + if *file == "" { + w = &buf + } else { + f, err := os.OpenFile(s.Path(*file), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) + if err != nil { + return "", "", fmt.Errorf("OpenFile(%s): %w", *file, err) + } + defer f.Close() + w = f + } + tbl, txn, err := getTable(db, tableName) + if err != nil { + return "", "", err + } + err = writeObjects(tbl, tbl.All(txn), w, cols, *format) + return buf.String(), "", err + }, nil + }) +} + +func CompareCmd(db *DB) script.Cmd { + return script.Command( + script.CmdUsage{ + Summary: "Compare table", + Args: "(-timeout=) (-grep=) table file", + }, + func(s *script.State, args ...string) (script.WaitFunc, error) { + flags := newCmdFlagSet() + timeout := flags.Duration("timeout", time.Second, "Maximum amount of time to wait for the table contents to match") + grep := flags.String("grep", "", "Grep the result rows and only compare matching ones") + err := flags.Parse(args) + args = flags.Args() + if err != nil || len(args) != 2 { + return nil, fmt.Errorf("%w: %s", errUsage, err) + } + + var grepRe *regexp.Regexp + if *grep != "" { + grepRe, err = regexp.Compile(*grep) + if err != nil { + return nil, fmt.Errorf("bad grep: %w", err) + } + } + + tableName := args[0] + + txn := db.ReadTxn() + meta := db.GetTable(txn, tableName) + if meta == nil { + return nil, fmt.Errorf("table %q not found", tableName) + } + tbl := AnyTable{Meta: meta} + header := tbl.TableHeader() + + data, err := os.ReadFile(s.Path(args[1])) + if err != nil { + return nil, fmt.Errorf("ReadFile(%s): %w", args[1], err) + } + lines := strings.Split(string(data), "\n") + lines = slices.DeleteFunc(lines, func(line string) bool { + return strings.TrimSpace(line) == "" + }) + if len(lines) < 1 { + return nil, fmt.Errorf("%q missing header line, e.g. %q", args[1], strings.Join(header, " ")) + } + + columnNames, columnPositions := splitHeaderLine(lines[0]) + columnIndexes, err := getColumnIndexes(columnNames, header) + if err != nil { + return nil, err + } + lines = lines[1:] + origLines := lines + timeoutChan := time.After(*timeout) + + for { + lines = origLines + + // Create the diff between 'lines' and the rows in the table. + equal := true + var diff bytes.Buffer + w := newTabWriter(&diff) + fmt.Fprintf(w, " %s\n", joinByPositions(columnNames, columnPositions)) + + objs, watch := tbl.AllWatch(db.ReadTxn()) + for obj := range objs { + rowRaw := takeColumns(obj.(TableWritable).TableRow(), columnIndexes) + row := joinByPositions(rowRaw, columnPositions) + if grepRe != nil && !grepRe.Match([]byte(row)) { + continue + } + + if len(lines) == 0 { + equal = false + fmt.Fprintf(w, "- %s\n", row) + continue + } + line := lines[0] + splitLine := splitByPositions(line, columnPositions) + + if slices.Equal(rowRaw, splitLine) { + fmt.Fprintf(w, " %s\n", row) + } else { + fmt.Fprintf(w, "- %s\n", row) + fmt.Fprintf(w, "+ %s\n", line) + equal = false + } + lines = lines[1:] + } + for _, line := range lines { + fmt.Fprintf(w, "+ %s\n", line) + equal = false + } + if equal { + return nil, nil + } + w.Flush() + + select { + case <-s.Context().Done(): + return nil, s.Context().Err() + + case <-timeoutChan: + return nil, fmt.Errorf("table mismatch:\n%s", diff.String()) + + case <-watch: + } + } + }) +} + +func InsertCmd(db *DB) script.Cmd { + return script.Command( + script.CmdUsage{ + Summary: "Insert object into a table", + Args: "table path...", + }, + func(s *script.State, args ...string) (script.WaitFunc, error) { + return insertOrDelete(true, db, s, args...) + }, + ) +} + +func DeleteCmd(db *DB) script.Cmd { + return script.Command( + script.CmdUsage{ + Summary: "Delete an object from the table", + Args: "table path...", + }, + func(s *script.State, args ...string) (script.WaitFunc, error) { + return insertOrDelete(false, db, s, args...) + }, + ) +} + +func getTable(db *DB, tableName string) (*AnyTable, ReadTxn, error) { + txn := db.ReadTxn() + meta := db.GetTable(txn, tableName) + if meta == nil { + return nil, nil, fmt.Errorf("table %q not found", tableName) + } + return &AnyTable{Meta: meta}, txn, nil +} + +func insertOrDelete(insert bool, db *DB, s *script.State, args ...string) (script.WaitFunc, error) { + if len(args) < 2 { + return nil, fmt.Errorf("%w: expected table and path(s)", errUsage) + } + + tbl, _, err := getTable(db, args[0]) + if err != nil { + return nil, err + } + + wtxn := db.WriteTxn(tbl.Meta) + defer wtxn.Commit() + + for _, arg := range args[1:] { + data, err := os.ReadFile(s.Path(arg)) + if err != nil { + return nil, fmt.Errorf("ReadFile(%s): %w", arg, err) + } + parts := strings.Split(string(data), "---") + for _, part := range parts { + obj, err := tbl.UnmarshalYAML([]byte(part)) + if err != nil { + return nil, fmt.Errorf("Unmarshal(%s): %w", arg, err) + } + if insert { + _, _, err = tbl.Insert(wtxn, obj) + if err != nil { + return nil, fmt.Errorf("Insert(%s): %w", arg, err) + } + } else { + _, _, err = tbl.Delete(wtxn, obj) + if err != nil { + return nil, fmt.Errorf("Delete(%s): %w", arg, err) + } + + } + } + } + return nil, nil +} + +func PrefixCmd(db *DB) script.Cmd { + return queryCmd(db, queryCmdPrefix, "Query table by prefix") +} + +func LowerBoundCmd(db *DB) script.Cmd { + return queryCmd(db, queryCmdLowerBound, "Query table by lower bound search") +} + +func ListCmd(db *DB) script.Cmd { + return queryCmd(db, queryCmdList, "List objects in the table") +} + +func GetCmd(db *DB) script.Cmd { + return queryCmd(db, queryCmdGet, "Get the first matching object") +} + +const ( + queryCmdList = iota + queryCmdPrefix + queryCmdLowerBound + queryCmdGet +) + +func queryCmd(db *DB, query int, summary string) script.Cmd { + return script.Command( + script.CmdUsage{ + Summary: summary, + Args: "(-o=) (-columns=col1,...) (-format={table*,yaml,json}) (-index=) table key", + }, + func(s *script.State, args ...string) (script.WaitFunc, error) { + return runQueryCmd(query, db, s, args) + }, + ) +} + +func runQueryCmd(query int, db *DB, s *script.State, args []string) (script.WaitFunc, error) { + flags := newCmdFlagSet() + file := flags.String("o", "", "File to write results to instead of stdout") + index := flags.String("index", "", "Index to query") + format := flags.String("format", "table", "Format to write in (table, yaml, json)") + columns := flags.String("columns", "", "Comma-separated list of columns to write") + delete := flags.Bool("delete", false, "Delete all matching objects") + if err := flags.Parse(args); err != nil { + return nil, fmt.Errorf("%w: %s", errUsage, err) + } + + var cols []string + if len(*columns) > 0 { + cols = strings.Split(*columns, ",") + } + + args = flags.Args() + if len(args) < 2 { + return nil, fmt.Errorf("%w: expected table and key", errUsage) + } + + return func(*script.State) (stdout, stderr string, err error) { + tbl, txn, err := getTable(db, args[0]) + if err != nil { + return "", "", err + } + + var buf strings.Builder + var w io.Writer + if *file == "" { + w = &buf + } else { + f, err := os.OpenFile(s.Path(*file), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) + if err != nil { + return "", "", fmt.Errorf("OpenFile(%s): %s", *file, err) + } + defer f.Close() + w = f + } + + var it iter.Seq2[any, uint64] + switch query { + case queryCmdList: + it, err = tbl.List(txn, *index, args[1]) + case queryCmdLowerBound: + it, err = tbl.LowerBound(txn, *index, args[1]) + case queryCmdPrefix: + it, err = tbl.Prefix(txn, *index, args[1]) + case queryCmdGet: + it, err = tbl.List(txn, *index, args[1]) + if err == nil { + it = firstOfSeq2(it) + } + default: + panic("unknown query enum") + } + if err != nil { + return "", "", fmt.Errorf("query: %w", err) + } + + err = writeObjects(tbl, it, w, cols, *format) + if err != nil { + return "", "", err + } + + if *delete { + wtxn := db.WriteTxn(tbl.Meta) + count := 0 + for obj := range it { + _, hadOld, err := tbl.Delete(wtxn, obj) + if err != nil { + wtxn.Abort() + return "", "", err + } + if hadOld { + count++ + } + } + s.Logf("Deleted %d objects\n", count) + wtxn.Commit() + } + + return buf.String(), "", err + }, nil +} + +func WatchCmd(db *DB) script.Cmd { + return script.Command( + script.CmdUsage{ + Summary: "Watch a table for changes", + Args: "table", + }, + func(s *script.State, args ...string) (script.WaitFunc, error) { + if len(args) < 1 { + return nil, fmt.Errorf("expected table name") + } + + tbl, _, err := getTable(db, args[0]) + if err != nil { + return nil, err + } + wtxn := db.WriteTxn(tbl.Meta) + iter, err := tbl.Changes(wtxn) + wtxn.Commit() + if err != nil { + return nil, err + } + + header := tbl.TableHeader() + if header == nil { + return nil, fmt.Errorf("objects in table %q not TableWritable", tbl.Meta.Name()) + } + tw := newTabWriter(&strikethroughWriter{w: s.LogWriter()}) + fmt.Fprintf(tw, "%s\n", strings.Join(header, "\t")) + + limiter := rate.NewLimiter(10.0, 1) + for { + if err := limiter.Wait(s.Context()); err != nil { + break + } + changes, watch := iter.nextAny(db.ReadTxn()) + for change := range changes { + row := change.Object.(TableWritable).TableRow() + if change.Deleted { + fmt.Fprintf(tw, "%s (deleted)%s", strings.Join(row, "\t"), magicStrikethroughNewline) + } else { + fmt.Fprintf(tw, "%s\n", strings.Join(row, "\t")) + } + } + tw.Flush() + if err := s.FlushLog(); err != nil { + return nil, err + } + select { + case <-watch: + case <-s.Context().Done(): + return nil, nil + } + } + return nil, nil + + }, + ) +} + +func firstOfSeq2[A, B any](it iter.Seq2[A, B]) iter.Seq2[A, B] { + return func(yield func(a A, b B) bool) { + for a, b := range it { + yield(a, b) + break + } + } +} + +func writeObjects(tbl *AnyTable, it iter.Seq2[any, Revision], w io.Writer, columns []string, format string) error { + if len(columns) > 0 && format != "table" { + return fmt.Errorf("-columns not supported with non-table formats") + } + switch format { + case "yaml": + sep := []byte("---\n") + first := true + for obj := range it { + if !first { + w.Write(sep) + } + first = false + + out, err := yaml.Marshal(obj) + if err != nil { + return fmt.Errorf("yaml.Marshal: %w", err) + } + if _, err := w.Write(out); err != nil { + return err + } + } + return nil + case "json": + sep := []byte("\n") + first := true + for obj := range it { + if !first { + w.Write(sep) + } + first = false + + out, err := json.Marshal(obj) + if err != nil { + return fmt.Errorf("json.Marshal: %w", err) + } + if _, err := w.Write(out); err != nil { + return err + } + } + return nil + case "table": + header := tbl.TableHeader() + if header == nil { + return fmt.Errorf("objects in table %q not TableWritable", tbl.Meta.Name()) + } + + var idxs []int + var err error + if len(columns) > 0 { + idxs, err = getColumnIndexes(columns, header) + header = columns + } else { + idxs, err = getColumnIndexes(header, header) + } + if err != nil { + return err + } + tw := newTabWriter(w) + fmt.Fprintf(tw, "%s\n", strings.Join(header, "\t")) + + for obj := range it { + row := takeColumns(obj.(TableWritable).TableRow(), idxs) + fmt.Fprintf(tw, "%s\n", strings.Join(row, "\t")) + } + return tw.Flush() + } + return fmt.Errorf("unknown format %q, expected table, yaml or json", format) +} + +func takeColumns[T any](xs []T, idxs []int) (out []T) { + for _, idx := range idxs { + out = append(out, xs[idx]) + } + return +} + +func getColumnIndexes(names []string, header []string) ([]int, error) { + columnIndexes := make([]int, 0, len(header)) +loop: + for _, name := range names { + for i, name2 := range header { + if strings.EqualFold(name, name2) { + columnIndexes = append(columnIndexes, i) + continue loop + } + } + return nil, fmt.Errorf("column %q not part of %v", name, header) + } + return columnIndexes, nil +} + +// splitHeaderLine takes a header of column names separated by any +// number of whitespaces and returns the names and their starting positions. +// e.g. "Foo Bar Baz" would result in ([Foo,Bar,Baz],[0,5,9]). +// With this information we can take a row in the database and format it +// the same way as our test data. +func splitHeaderLine(line string) (names []string, pos []int) { + start := 0 + skip := true + for i, r := range line { + switch r { + case ' ', '\t': + if !skip { + names = append(names, line[start:i]) + pos = append(pos, start) + start = -1 + } + skip = true + default: + skip = false + if start == -1 { + start = i + } + } + } + if start >= 0 && start < len(line) { + names = append(names, line[start:]) + pos = append(pos, start) + } + return +} + +// splitByPositions takes a "row" line and the positions of the header columns +// and extracts the values. +// e.g. if we have the positions [0,5,9] (from header "Foo Bar Baz") and +// line is "1 a b", then we'd extract [1,a,b]. +// The whitespace on the right of the start position (e.g. "1 \t") is trimmed. +// This of course requires that the table is properly formatted in a way that the +// header columns are indented to fit the data exactly. +func splitByPositions(line string, positions []int) []string { + out := make([]string, 0, len(positions)) + start := 0 + for _, pos := range positions[1:] { + if start >= len(line) { + out = append(out, "") + start = len(line) + continue + } + out = append(out, strings.TrimRight(line[start:min(pos, len(line))], " \t")) + start = pos + } + out = append(out, strings.TrimRight(line[min(start, len(line)):], " \t")) + return out +} + +// joinByPositions is the reverse of splitByPositions, it takes the columns of a +// row and the starting positions of each and joins into a single line. +// e.g. [1,a,b] and positions [0,5,9] expands to "1 a b". +// NOTE: This does not deal well with mixing tabs and spaces. The test input +// data should preferably just use spaces. +func joinByPositions(row []string, positions []int) string { + var w strings.Builder + prev := 0 + for i, pos := range positions { + for pad := pos - prev; pad > 0; pad-- { + w.WriteByte(' ') + } + w.WriteString(row[i]) + prev = pos + len(row[i]) + } + return w.String() +} + +// strikethroughWriter writes a line of text that is striken through +// if the line contains the magic character at the end before \n. +// This is used to strike through a tab-formatted line without messing +// up with the widths of the cells. +type strikethroughWriter struct { + buf []byte + strikethrough bool + w io.Writer +} + +var ( + // Magic character to use at the end of the line to denote that this should be + // striken through. + // This is to avoid messing up the width calculations in the tab writer, which + // would happen if ANSI codes were used directly. + magicStrikethrough = byte('\xfe') + magicStrikethroughNewline = "\xfe\n" +) + +func stripTrailingWhitespace(buf []byte) []byte { + idx := bytes.LastIndexFunc( + buf, + func(r rune) bool { + return r != ' ' && r != '\t' + }, + ) + if idx > 0 { + return buf[:idx+1] + } + return buf +} + +func (s *strikethroughWriter) Write(p []byte) (n int, err error) { + write := func(bs []byte) { + if err == nil { + _, e := s.w.Write(bs) + if e != nil { + err = e + } + } + } + for _, c := range p { + switch c { + case '\n': + s.buf = stripTrailingWhitespace(s.buf) + + if s.strikethrough { + write(beginStrikethrough) + write(s.buf) + write(endStrikethrough) + } else { + write(s.buf) + } + write(newline) + + s.buf = s.buf[:0] // reset len for reuse. + s.strikethrough = false + + if err != nil { + return 0, err + } + + case magicStrikethrough: + s.strikethrough = true + + default: + s.buf = append(s.buf, c) + } + } + return len(p), nil +} + +var ( + // Use color red and the strikethrough escape + beginStrikethrough = []byte("\033[9m\033[31m") + endStrikethrough = []byte("\033[0m") + newline = []byte("\n") +) + +var _ io.Writer = &strikethroughWriter{} + +func newTabWriter(out io.Writer) *tabwriter.Writer { + const ( + minWidth = 5 + width = 4 + padding = 3 + padChar = ' ' + flags = tabwriter.RememberWidths + ) + return tabwriter.NewWriter(out, minWidth, width, padding, padChar, flags) +} diff --git a/vendor/github.com/cilium/statedb/table.go b/vendor/github.com/cilium/statedb/table.go index 1402e9360c..1d75f07241 100644 --- a/vendor/github.com/cilium/statedb/table.go +++ b/vendor/github.com/cilium/statedb/table.go @@ -9,11 +9,14 @@ import ( "regexp" "runtime" "slices" + "sort" "strings" "sync" + "sync/atomic" "github.com/cilium/statedb/internal" "github.com/cilium/statedb/part" + "gopkg.in/yaml.v3" "github.com/cilium/statedb/index" ) @@ -45,7 +48,8 @@ func NewTable[Obj any]( fromObject: func(iobj object) index.KeySet { return idx.fromObject(iobj.data.(Obj)) }, - unique: idx.isUnique(), + fromString: idx.fromString, + unique: idx.isUnique(), } } @@ -128,6 +132,15 @@ type genTable[Obj any] struct { primaryAnyIndexer anyIndexer secondaryAnyIndexers map[string]anyIndexer indexPositions map[string]int + lastWriteTxn atomic.Pointer[txn] +} + +func (t *genTable[Obj]) acquired(txn *txn) { + t.lastWriteTxn.Store(txn) +} + +func (t *genTable[Obj]) getAcquiredInfo() string { + return t.lastWriteTxn.Load().acquiredInfo() } func (t *genTable[Obj]) tableEntry() tableEntry { @@ -168,6 +181,16 @@ func (t *genTable[Obj]) indexPos(name string) int { return t.indexPositions[name] } +func (t *genTable[Obj]) getIndexer(name string) *anyIndexer { + if name == "" || t.primaryAnyIndexer.name == name { + return &t.primaryAnyIndexer + } + if indexer, ok := t.secondaryAnyIndexers[name]; ok { + return &indexer + } + return nil +} + func (t *genTable[Obj]) PrimaryIndexer() Indexer[Obj] { return t.primaryIndexer } @@ -184,6 +207,16 @@ func (t *genTable[Obj]) Name() string { return t.table } +func (t *genTable[Obj]) Indexes() []string { + idxs := make([]string, 0, 1+len(t.secondaryAnyIndexers)) + idxs = append(idxs, t.primaryAnyIndexer.name) + for k := range t.secondaryAnyIndexers { + idxs = append(idxs, k) + } + sort.Strings(idxs) + return idxs +} + func (t *genTable[Obj]) ToTable() Table[Obj] { return t } @@ -233,6 +266,11 @@ func (t *genTable[Obj]) NumObjects(txn ReadTxn) int { return table.numObjects() } +func (t *genTable[Obj]) numDeletedObjects(txn ReadTxn) int { + table := txn.getTxn().getTableEntry(t) + return table.numDeletedObjects() +} + func (t *genTable[Obj]) Get(txn ReadTxn, q Query[Obj]) (obj Obj, revision uint64, ok bool) { obj, revision, _, ok = t.GetWatch(txn, q) return @@ -283,7 +321,7 @@ func (t *genTable[Obj]) GetWatch(txn ReadTxn, q Query[Obj]) (obj Obj, revision u } // Check that we have a full match on the key - _, secondary := decodeNonUniqueKey(key) + secondary, _ := decodeNonUniqueKey(key) if len(secondary) == len(q.key) { break } @@ -308,7 +346,10 @@ func (t *genTable[Obj]) LowerBoundWatch(txn ReadTxn, q Query[Obj]) (iter.Seq2[Ob // we watch the whole table for changes. watch := indexTxn.RootWatch() iter := indexTxn.LowerBound(q.key) - return partSeq[Obj](iter), watch + if indexTxn.unique { + return partSeq[Obj](iter), watch + } + return nonUniqueLowerBoundSeq[Obj](iter, q.key), watch } func (t *genTable[Obj]) Prefix(txn ReadTxn, q Query[Obj]) iter.Seq2[Obj, Revision] { @@ -319,7 +360,10 @@ func (t *genTable[Obj]) Prefix(txn ReadTxn, q Query[Obj]) iter.Seq2[Obj, Revisio func (t *genTable[Obj]) PrefixWatch(txn ReadTxn, q Query[Obj]) (iter.Seq2[Obj, Revision], <-chan struct{}) { indexTxn := txn.getTxn().mustIndexReadTxn(t, t.indexPos(q.index)) iter, watch := indexTxn.Prefix(q.key) - return partSeq[Obj](iter), watch + if indexTxn.unique { + return partSeq[Obj](iter), watch + } + return nonUniqueSeq[Obj](iter, true, q.key), watch } func (t *genTable[Obj]) All(txn ReadTxn) iter.Seq2[Obj, Revision] { @@ -356,7 +400,7 @@ func (t *genTable[Obj]) ListWatch(txn ReadTxn, q Query[Obj]) (iter.Seq2[Obj, Rev // iteration will continue until key length mismatches, e.g. we hit a // longer key sharing the same prefix. iter, watch := indexTxn.Prefix(q.key) - return nonUniqueSeq[Obj](iter, q.key), watch + return nonUniqueSeq[Obj](iter, false, q.key), watch } func (t *genTable[Obj]) Insert(txn WriteTxn, obj Obj) (oldObj Obj, hadOld bool, err error) { @@ -468,5 +512,18 @@ func (t *genTable[Obj]) sortableMutex() internal.SortableMutex { return t.smu } +func (t *genTable[Obj]) proto() any { + var zero Obj + return zero +} + +func (t *genTable[Obj]) unmarshalYAML(data []byte) (any, error) { + var obj Obj + if err := yaml.Unmarshal(data, &obj); err != nil { + return nil, err + } + return obj, nil +} + var _ Table[bool] = &genTable[bool]{} var _ RWTable[bool] = &genTable[bool]{} diff --git a/vendor/github.com/cilium/statedb/txn.go b/vendor/github.com/cilium/statedb/txn.go index bc4fb48cb3..4a7bc1c767 100644 --- a/vendor/github.com/cilium/statedb/txn.go +++ b/vendor/github.com/cilium/statedb/txn.go @@ -12,6 +12,7 @@ import ( "reflect" "runtime" "slices" + "sync/atomic" "time" "github.com/cilium/statedb/index" @@ -20,12 +21,18 @@ import ( ) type txn struct { - db *DB - handle string - root dbRoot + db *DB + root dbRoot + + handle string + acquiredAt time.Time // the time at which the transaction acquired the locks + duration atomic.Uint64 // the transaction duration after it finished + writeTxn +} + +type writeTxn struct { modifiedTables []*tableEntry // table entries being modified smus internal.SortableMutexes // the (sorted) table locks - acquiredAt time.Time // the time at which the transaction acquired the locks tableNames []string } @@ -46,6 +53,23 @@ func (txn *txn) getTxn() *txn { return txn } +// acquiredInfo returns the information for the "Last WriteTxn" column +// in "db tables" command. The correctness of this relies on the following assumptions: +// - txn.handle and txn.acquiredAt are not modified +// - txn.duration is atomically updated on Commit or Abort +func (txn *txn) acquiredInfo() string { + if txn == nil { + return "" + } + since := internal.PrettySince(txn.acquiredAt) + dur := time.Duration(txn.duration.Load()) + if txn.duration.Load() == 0 { + // Still locked + return fmt.Sprintf("%s (locked for %s)", txn.handle, since) + } + return fmt.Sprintf("%s (%s ago, locked for %s)", txn.handle, since, internal.PrettyDuration(dur)) +} + // txnFinalizer is called when the GC frees *txn. It checks that a WriteTxn // has been Aborted or Committed. This is a safeguard against forgetting to // Abort/Commit which would cause the table to be locked forever. @@ -336,22 +360,59 @@ func (txn *txn) delete(meta TableMeta, guardRevision Revision, data any) (object return obj, true, nil } +const ( + nonUniqueSeparator = 0x0 + nonUniqueSubstitute = 0xfe + nonUniqueSubstitute2 = 0xfd +) + +// appendEncodePrimary encodes the 'src' (primary key) into 'dst'. +func appendEncodePrimary(dst, src []byte) []byte { + for _, b := range src { + switch b { + case nonUniqueSeparator: + dst = append(dst, nonUniqueSubstitute) + case nonUniqueSubstitute: + dst = append(dst, nonUniqueSubstitute2, 0x00) + case nonUniqueSubstitute2: + dst = append(dst, nonUniqueSubstitute2, 0x01) + default: + dst = append(dst, b) + } + } + return dst +} + // encodeNonUniqueKey constructs the internal key to use with non-unique indexes. -// It concatenates the secondary key with the primary key and the length of the secondary key. -// The length is stored as unsigned 16-bit big endian. -// This allows looking up from the non-unique index with the secondary key by doing a prefix -// search. The length is used to safe-guard against indexers that don't terminate the key -// properly (e.g. if secondary key is "foo", then we don't want "foobar" to match). +// The key is constructed by concatenating the secondary key with the primary key +// along with the secondary key length. The secondary and primary key are separated +// with by a 0x0 to ensure ordering is defined by the secondary key. To make sure the +// separator does not appear in the primary key it is encoded using this schema: +// +// 0x0 => 0xfe, 0xfe => 0xfd00, 0xfd => 0xfd01 +// +// The schema tries to avoid expansion for encoded small integers, e.g. 0x0000 becomes 0xfefe. +// The length at the end is encoded as unsigned 16-bit big endian. +// +// This schema allows looking up from the non-unique index with the secondary key by +// doing a prefix search. The length is used to safe-guard against indexers that don't +// terminate the key properly (e.g. if secondary key is "foo", then we don't want +// "foobar" to match). func encodeNonUniqueKey(primary, secondary index.Key) []byte { - key := make([]byte, 0, len(secondary)+len(primary)+2) + key := make([]byte, 0, + len(secondary)+1 /* separator */ + + len(primary)+ + 2 /* space for few substitutions */ + + 2 /* length */) key = append(key, secondary...) - key = append(key, primary...) + key = append(key, nonUniqueSeparator) + key = appendEncodePrimary(key, primary) // KeySet limits size of key to 16 bits. return binary.BigEndian.AppendUint16(key, uint16(len(secondary))) } -func decodeNonUniqueKey(key []byte) (primary []byte, secondary []byte) { - // Multi-index key is [, , ] +func decodeNonUniqueKey(key []byte) (secondary []byte, encPrimary []byte) { + // Non-unique key is [, '\xfe', , ] if len(key) < 2 { return nil, nil } @@ -359,13 +420,13 @@ func decodeNonUniqueKey(key []byte) (primary []byte, secondary []byte) { if len(key) < secondaryLength { return nil, nil } - return key[secondaryLength : len(key)-2], key[:secondaryLength] + return key[:secondaryLength], key[secondaryLength+1 : len(key)-2] } func (txn *txn) Abort() { runtime.SetFinalizer(txn, nil) - // If writeTxns is nil, this transaction has already been committed or aborted, and + // If modifiedTables is nil, this transaction has already been committed or aborted, and // thus there is nothing to do. We allow this without failure to allow for defer // pattern: // @@ -384,13 +445,15 @@ func (txn *txn) Abort() { return } + txn.duration.Store(uint64(time.Since(txn.acquiredAt))) + txn.smus.Unlock() txn.db.metrics.WriteTxnDuration( txn.handle, txn.tableNames, time.Since(txn.acquiredAt)) - *txn = zeroTxn + txn.writeTxn = writeTxn{} } // Commit the transaction. Returns a ReadTxn that is the snapshot of the database at the @@ -422,6 +485,8 @@ func (txn *txn) Commit() ReadTxn { return nil } + txn.duration.Store(uint64(time.Since(txn.acquiredAt))) + db := txn.db // Commit each individual changed index to each table. @@ -477,6 +542,7 @@ func (txn *txn) Commit() ReadTxn { // Commit the transaction to build the new root tree and then // atomically store it. + txn.root = root db.root.Store(&root) db.mu.Unlock() @@ -499,11 +565,8 @@ func (txn *txn) Commit() ReadTxn { txn.tableNames, time.Since(txn.acquiredAt)) - // Zero out the transaction to make it inert and - // convert it into a ReadTxn. - *txn = zeroTxn - txn.db = db - txn.root = root + // Convert into a ReadTxn + txn.writeTxn = writeTxn{} return txn } diff --git a/vendor/github.com/cilium/statedb/types.go b/vendor/github.com/cilium/statedb/types.go index 5f817f8e71..5492e64f1d 100644 --- a/vendor/github.com/cilium/statedb/types.go +++ b/vendor/github.com/cilium/statedb/types.go @@ -4,6 +4,7 @@ package statedb import ( + "errors" "io" "iter" @@ -28,22 +29,6 @@ type Table[Obj any] interface { // Useful for generic utilities that need access to the primary key. PrimaryIndexer() Indexer[Obj] - // NumObjects returns the number of objects stored in the table. - NumObjects(ReadTxn) int - - // Initialized returns true if in this ReadTxn (snapshot of the database) - // the registered initializers have all been completed. The returned - // watch channel will be closed when the table becomes initialized. - Initialized(ReadTxn) (bool, <-chan struct{}) - - // PendingInitializers returns the set of pending initializers that - // have not yet completed. - PendingInitializers(ReadTxn) []string - - // Revision of the table. Constant for a read transaction, but - // increments in a write transaction on each Insert and Delete. - Revision(ReadTxn) Revision - // All returns a sequence of all objects in the table. All(ReadTxn) iter.Seq2[Obj, Revision] @@ -215,24 +200,48 @@ type RWTable[Obj any] interface { // TableMeta provides information about the table that is independent of // the object type (the 'Obj' constraint). type TableMeta interface { - Name() TableName // The name of the table + // Name returns the name of the table + Name() TableName + + // Indexes returns the names of the indexes + Indexes() []string + + // NumObjects returns the number of objects stored in the table. + NumObjects(ReadTxn) int + // Initialized returns true if in this ReadTxn (snapshot of the database) + // the registered initializers have all been completed. The returned + // watch channel will be closed when the table becomes initialized. + Initialized(ReadTxn) (bool, <-chan struct{}) + + // PendingInitializers returns the set of pending initializers that + // have not yet completed. + PendingInitializers(ReadTxn) []string + + // Revision of the table. Constant for a read transaction, but + // increments in a write transaction on each Insert and Delete. + Revision(ReadTxn) Revision + + // Internal unexported methods used only internally. + tableInternal +} + +type tableInternal interface { tableEntry() tableEntry tablePos() int setTablePos(int) indexPos(string) int - tableKey() []byte // The radix key for the table in the root tree + tableKey() []byte // The radix key for the table in the root tree + getIndexer(name string) *anyIndexer primary() anyIndexer // The untyped primary indexer for the table secondary() map[string]anyIndexer // Secondary indexers (if any) sortableMutex() internal.SortableMutex // The sortable mutex for locking the table for writing anyChanges(txn WriteTxn) (anyChangeIterator, error) -} - -// Iterator for iterating objects returned from queries. -type Iterator[Obj any] interface { - // Next returns the next object and its revision if ok is true, otherwise - // zero values to mean that the iteration has finished. - Next() (obj Obj, rev Revision, ok bool) + proto() any // Returns the zero value of 'Obj', e.g. the prototype + unmarshalYAML(data []byte) (any, error) // Unmarshal the data into 'Obj' + numDeletedObjects(txn ReadTxn) int // Number of objects in graveyard + acquired(*txn) + getAcquiredInfo() string } type ReadTxn interface { @@ -278,10 +287,26 @@ func ByRevision[Obj any](rev uint64) Query[Obj] { // Index implements the indexing of objects (FromObjects) and querying of objects from the index (FromKey) type Index[Obj any, Key any] struct { - Name string + // Name of the index + Name string + + // FromObject extracts key(s) from the object. The key set + // can contain 0, 1 or more keys. FromObject func(obj Obj) index.KeySet - FromKey func(key Key) index.Key - Unique bool + + // FromKey converts the index key into a raw key. + // With this we can perform Query() against this index with + // the [Key] type. + FromKey func(key Key) index.Key + + // FromString is an optional conversion from string to a raw key. + // If implemented allows script commands to query with this index. + FromString func(key string) (index.Key, error) + + // Unique marks the index as unique. Primary index must always be + // unique. A secondary index may be non-unique in which case a single + // key may map to multiple objects. + Unique bool } var _ Indexer[struct{}] = &Index[struct{}, bool]{} @@ -299,6 +324,17 @@ func (i Index[Obj, Key]) fromObject(obj Obj) index.KeySet { return i.FromObject(obj) } +var errFromStringNil = errors.New("FromString not defined") + +//nolint:unused +func (i Index[Obj, Key]) fromString(s string) (index.Key, error) { + if i.FromString == nil { + return index.Key{}, errFromStringNil + } + k, err := i.FromString(s) + return k, err +} + //nolint:unused func (i Index[Obj, Key]) isUnique() bool { return i.Unique @@ -329,6 +365,7 @@ type Indexer[Obj any] interface { indexName() string isUnique() bool fromObject(Obj) index.KeySet + fromString(string) (index.Key, error) ObjectToKey(Obj) index.Key QueryFromObject(Obj) Query[Obj] @@ -379,6 +416,9 @@ type anyIndexer struct { // object with. fromObject func(object) index.KeySet + // fromString converts string into a key. Optional. + fromString func(string) (index.Key, error) + // unique if true will index the object solely on the // values returned by fromObject. If false the primary // key of the object will be appended to the key. diff --git a/vendor/github.com/fatih/color/README.md b/vendor/github.com/fatih/color/README.md index be82827cac..d135bfe023 100644 --- a/vendor/github.com/fatih/color/README.md +++ b/vendor/github.com/fatih/color/README.md @@ -9,7 +9,7 @@ suits you. ## Install -```bash +``` go get github.com/fatih/color ``` @@ -30,6 +30,18 @@ color.Magenta("And many others ..") ``` +### RGB colors + +If your terminal supports 24-bit colors, you can use RGB color codes. + +```go +color.RGB(255, 128, 0).Println("foreground orange") +color.RGB(230, 42, 42).Println("foreground red") + +color.BgRGB(255, 128, 0).Println("background orange") +color.BgRGB(230, 42, 42).Println("background red") +``` + ### Mix and reuse colors ```go @@ -49,6 +61,11 @@ boldRed.Println("This will print text in bold red.") whiteBackground := red.Add(color.BgWhite) whiteBackground.Println("Red text with white background.") + +// Mix with RGB color codes +color.RGB(255, 128, 0).AddBgRGB(0, 0, 0).Println("orange with black background") + +color.BgRGB(255, 128, 0).AddRGB(255, 255, 255).Println("orange background with white foreground") ``` ### Use your own output (io.Writer) @@ -161,10 +178,6 @@ c.Println("This prints again cyan...") To output color in GitHub Actions (or other CI systems that support ANSI colors), make sure to set `color.NoColor = false` so that it bypasses the check for non-tty output streams. -## Todo - -* Save/Return previous values -* Evaluate fmt.Formatter interface ## Credits diff --git a/vendor/github.com/fatih/color/color.go b/vendor/github.com/fatih/color/color.go index 81094e87c5..ee39b408e9 100644 --- a/vendor/github.com/fatih/color/color.go +++ b/vendor/github.com/fatih/color/color.go @@ -98,6 +98,9 @@ const ( FgMagenta FgCyan FgWhite + + // used internally for 256 and 24-bit coloring + foreground ) // Foreground Hi-Intensity text colors @@ -122,6 +125,9 @@ const ( BgMagenta BgCyan BgWhite + + // used internally for 256 and 24-bit coloring + background ) // Background Hi-Intensity text colors @@ -150,6 +156,30 @@ func New(value ...Attribute) *Color { return c } +// RGB returns a new foreground color in 24-bit RGB. +func RGB(r, g, b int) *Color { + return New(foreground, 2, Attribute(r), Attribute(g), Attribute(b)) +} + +// BgRGB returns a new background color in 24-bit RGB. +func BgRGB(r, g, b int) *Color { + return New(background, 2, Attribute(r), Attribute(g), Attribute(b)) +} + +// AddRGB is used to chain foreground RGB SGR parameters. Use as many as parameters to combine +// and create custom color objects. Example: .Add(34, 0, 12).Add(255, 128, 0). +func (c *Color) AddRGB(r, g, b int) *Color { + c.params = append(c.params, foreground, 2, Attribute(r), Attribute(g), Attribute(b)) + return c +} + +// AddRGB is used to chain background RGB SGR parameters. Use as many as parameters to combine +// and create custom color objects. Example: .Add(34, 0, 12).Add(255, 128, 0). +func (c *Color) AddBgRGB(r, g, b int) *Color { + c.params = append(c.params, background, 2, Attribute(r), Attribute(g), Attribute(b)) + return c +} + // Set sets the given parameters immediately. It will change the color of // output with the given SGR parameters until color.Unset() is called. func Set(p ...Attribute) *Color { @@ -401,7 +431,7 @@ func (c *Color) format() string { func (c *Color) unformat() string { //return fmt.Sprintf("%s[%dm", escape, Reset) - //for each element in sequence let's use the speficic reset escape, ou the generic one if not found + //for each element in sequence let's use the specific reset escape, or the generic one if not found format := make([]string, len(c.params)) for i, v := range c.params { format[i] = strconv.Itoa(int(Reset)) diff --git a/vendor/github.com/prometheus/common/model/labelset_string.go b/vendor/github.com/prometheus/common/model/labelset_string.go index 481c47b46e..abb2c90018 100644 --- a/vendor/github.com/prometheus/common/model/labelset_string.go +++ b/vendor/github.com/prometheus/common/model/labelset_string.go @@ -11,8 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build go1.21 - package model import ( diff --git a/vendor/github.com/prometheus/common/model/labelset_string_go120.go b/vendor/github.com/prometheus/common/model/labelset_string_go120.go deleted file mode 100644 index c4212685e7..0000000000 --- a/vendor/github.com/prometheus/common/model/labelset_string_go120.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2024 The Prometheus 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. - -//go:build !go1.21 - -package model - -import ( - "fmt" - "sort" - "strings" -) - -// String was optimized using functions not available for go 1.20 -// or lower. We keep the old implementation for compatibility with client_golang. -// Once client golang drops support for go 1.20 (scheduled for August 2024), this -// file can be removed. -func (l LabelSet) String() string { - labelNames := make([]string, 0, len(l)) - for name := range l { - labelNames = append(labelNames, string(name)) - } - sort.Strings(labelNames) - lstrs := make([]string, 0, len(l)) - for _, name := range labelNames { - lstrs = append(lstrs, fmt.Sprintf("%s=%q", name, l[LabelName(name)])) - } - return fmt.Sprintf("{%s}", strings.Join(lstrs, ", ")) -} diff --git a/vendor/github.com/vishvananda/netlink/addr_linux.go b/vendor/github.com/vishvananda/netlink/addr_linux.go index 218ab23796..9b49baf976 100644 --- a/vendor/github.com/vishvananda/netlink/addr_linux.go +++ b/vendor/github.com/vishvananda/netlink/addr_linux.go @@ -1,6 +1,7 @@ package netlink import ( + "errors" "fmt" "net" "strings" @@ -169,6 +170,9 @@ func (h *Handle) addrHandle(link Link, addr *Addr, req *nl.NetlinkRequest) error // AddrList gets a list of IP addresses in the system. // Equivalent to: `ip addr show`. // The list can be filtered by link and ip family. +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func AddrList(link Link, family int) ([]Addr, error) { return pkgHandle.AddrList(link, family) } @@ -176,14 +180,17 @@ func AddrList(link Link, family int) ([]Addr, error) { // AddrList gets a list of IP addresses in the system. // Equivalent to: `ip addr show`. // The list can be filtered by link and ip family. +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) AddrList(link Link, family int) ([]Addr, error) { req := h.newNetlinkRequest(unix.RTM_GETADDR, unix.NLM_F_DUMP) msg := nl.NewIfAddrmsg(family) req.AddData(msg) - msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWADDR) - if err != nil { - return nil, err + msgs, executeErr := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWADDR) + if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { + return nil, executeErr } indexFilter := 0 @@ -212,7 +219,7 @@ func (h *Handle) AddrList(link Link, family int) ([]Addr, error) { res = append(res, addr) } - return res, nil + return res, executeErr } func parseAddr(m []byte) (addr Addr, family int, err error) { diff --git a/vendor/github.com/vishvananda/netlink/bridge_linux.go b/vendor/github.com/vishvananda/netlink/bridge_linux.go index 6c340b0ce9..fa5766b801 100644 --- a/vendor/github.com/vishvananda/netlink/bridge_linux.go +++ b/vendor/github.com/vishvananda/netlink/bridge_linux.go @@ -1,6 +1,7 @@ package netlink import ( + "errors" "fmt" "github.com/vishvananda/netlink/nl" @@ -9,21 +10,27 @@ import ( // BridgeVlanList gets a map of device id to bridge vlan infos. // Equivalent to: `bridge vlan show` +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func BridgeVlanList() (map[int32][]*nl.BridgeVlanInfo, error) { return pkgHandle.BridgeVlanList() } // BridgeVlanList gets a map of device id to bridge vlan infos. // Equivalent to: `bridge vlan show` +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) BridgeVlanList() (map[int32][]*nl.BridgeVlanInfo, error) { req := h.newNetlinkRequest(unix.RTM_GETLINK, unix.NLM_F_DUMP) msg := nl.NewIfInfomsg(unix.AF_BRIDGE) req.AddData(msg) req.AddData(nl.NewRtAttr(unix.IFLA_EXT_MASK, nl.Uint32Attr(uint32(nl.RTEXT_FILTER_BRVLAN)))) - msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWLINK) - if err != nil { - return nil, err + msgs, executeErr := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWLINK) + if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { + return nil, executeErr } ret := make(map[int32][]*nl.BridgeVlanInfo) for _, m := range msgs { @@ -51,7 +58,7 @@ func (h *Handle) BridgeVlanList() (map[int32][]*nl.BridgeVlanInfo, error) { } } } - return ret, nil + return ret, executeErr } // BridgeVlanAdd adds a new vlan filter entry diff --git a/vendor/github.com/vishvananda/netlink/chain_linux.go b/vendor/github.com/vishvananda/netlink/chain_linux.go index d9f441613c..5008e7101f 100644 --- a/vendor/github.com/vishvananda/netlink/chain_linux.go +++ b/vendor/github.com/vishvananda/netlink/chain_linux.go @@ -1,6 +1,8 @@ package netlink import ( + "errors" + "github.com/vishvananda/netlink/nl" "golang.org/x/sys/unix" ) @@ -56,6 +58,9 @@ func (h *Handle) chainModify(cmd, flags int, link Link, chain Chain) error { // ChainList gets a list of chains in the system. // Equivalent to: `tc chain list`. // The list can be filtered by link. +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func ChainList(link Link, parent uint32) ([]Chain, error) { return pkgHandle.ChainList(link, parent) } @@ -63,6 +68,9 @@ func ChainList(link Link, parent uint32) ([]Chain, error) { // ChainList gets a list of chains in the system. // Equivalent to: `tc chain list`. // The list can be filtered by link. +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) ChainList(link Link, parent uint32) ([]Chain, error) { req := h.newNetlinkRequest(unix.RTM_GETCHAIN, unix.NLM_F_DUMP) index := int32(0) @@ -78,9 +86,9 @@ func (h *Handle) ChainList(link Link, parent uint32) ([]Chain, error) { } req.AddData(msg) - msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWCHAIN) - if err != nil { - return nil, err + msgs, executeErr := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWCHAIN) + if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { + return nil, executeErr } var res []Chain @@ -108,5 +116,5 @@ func (h *Handle) ChainList(link Link, parent uint32) ([]Chain, error) { res = append(res, chain) } - return res, nil + return res, executeErr } diff --git a/vendor/github.com/vishvananda/netlink/class_linux.go b/vendor/github.com/vishvananda/netlink/class_linux.go index a82eb09de2..08fb16c2bc 100644 --- a/vendor/github.com/vishvananda/netlink/class_linux.go +++ b/vendor/github.com/vishvananda/netlink/class_linux.go @@ -201,14 +201,20 @@ func classPayload(req *nl.NetlinkRequest, class Class) error { // ClassList gets a list of classes in the system. // Equivalent to: `tc class show`. +// // Generally returns nothing if link and parent are not specified. +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func ClassList(link Link, parent uint32) ([]Class, error) { return pkgHandle.ClassList(link, parent) } // ClassList gets a list of classes in the system. // Equivalent to: `tc class show`. +// // Generally returns nothing if link and parent are not specified. +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) ClassList(link Link, parent uint32) ([]Class, error) { req := h.newNetlinkRequest(unix.RTM_GETTCLASS, unix.NLM_F_DUMP) msg := &nl.TcMsg{ @@ -222,9 +228,9 @@ func (h *Handle) ClassList(link Link, parent uint32) ([]Class, error) { } req.AddData(msg) - msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWTCLASS) - if err != nil { - return nil, err + msgs, executeErr := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWTCLASS) + if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { + return nil, executeErr } var res []Class @@ -295,7 +301,7 @@ func (h *Handle) ClassList(link Link, parent uint32) ([]Class, error) { res = append(res, class) } - return res, nil + return res, executeErr } func parseHtbClassData(class Class, data []syscall.NetlinkRouteAttr) (bool, error) { diff --git a/vendor/github.com/vishvananda/netlink/conntrack_linux.go b/vendor/github.com/vishvananda/netlink/conntrack_linux.go index ba022453b3..69c5eca034 100644 --- a/vendor/github.com/vishvananda/netlink/conntrack_linux.go +++ b/vendor/github.com/vishvananda/netlink/conntrack_linux.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "net" + "strings" "time" "github.com/vishvananda/netlink/nl" @@ -44,6 +45,9 @@ type InetFamily uint8 // ConntrackTableList returns the flow list of a table of a specific family // conntrack -L [table] [options] List conntrack or expectation table +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func ConntrackTableList(table ConntrackTableType, family InetFamily) ([]*ConntrackFlow, error) { return pkgHandle.ConntrackTableList(table, family) } @@ -70,7 +74,7 @@ func ConntrackUpdate(table ConntrackTableType, family InetFamily, flow *Conntrac // ConntrackDeleteFilter deletes entries on the specified table on the base of the filter // conntrack -D [table] parameters Delete conntrack or expectation // -// Deprecated: use [ConntrackDeleteFilter] instead. +// Deprecated: use [ConntrackDeleteFilters] instead. func ConntrackDeleteFilter(table ConntrackTableType, family InetFamily, filter CustomConntrackFilter) (uint, error) { return pkgHandle.ConntrackDeleteFilters(table, family, filter) } @@ -83,10 +87,13 @@ func ConntrackDeleteFilters(table ConntrackTableType, family InetFamily, filters // ConntrackTableList returns the flow list of a table of a specific family using the netlink handle passed // conntrack -L [table] [options] List conntrack or expectation table +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) ConntrackTableList(table ConntrackTableType, family InetFamily) ([]*ConntrackFlow, error) { - res, err := h.dumpConntrackTable(table, family) - if err != nil { - return nil, err + res, executeErr := h.dumpConntrackTable(table, family) + if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { + return nil, executeErr } // Deserialize all the flows @@ -95,7 +102,7 @@ func (h *Handle) ConntrackTableList(table ConntrackTableType, family InetFamily) result = append(result, parseRawData(dataRaw)) } - return result, nil + return result, executeErr } // ConntrackTableFlush flushes all the flows of a specified table using the netlink handle passed @@ -158,6 +165,7 @@ func (h *Handle) ConntrackDeleteFilters(table ConntrackTableType, family InetFam } var matched uint + var errMsgs []string for _, dataRaw := range res { flow := parseRawData(dataRaw) for _, filter := range filters { @@ -165,14 +173,18 @@ func (h *Handle) ConntrackDeleteFilters(table ConntrackTableType, family InetFam req2 := h.newConntrackRequest(table, family, nl.IPCTNL_MSG_CT_DELETE, unix.NLM_F_ACK) // skip the first 4 byte that are the netfilter header, the newConntrackRequest is adding it already req2.AddRawData(dataRaw[4:]) - req2.Execute(unix.NETLINK_NETFILTER, 0) - matched++ - // flow is already deleted, no need to match on other filters and continue to the next flow. - break + if _, err = req2.Execute(unix.NETLINK_NETFILTER, 0); err == nil { + matched++ + // flow is already deleted, no need to match on other filters and continue to the next flow. + break + } + errMsgs = append(errMsgs, fmt.Sprintf("failed to delete conntrack flow '%s': %s", flow.String(), err.Error())) } } } - + if len(errMsgs) > 0 { + return matched, fmt.Errorf(strings.Join(errMsgs, "; ")) + } return matched, nil } diff --git a/vendor/github.com/vishvananda/netlink/conntrack_unspecified.go b/vendor/github.com/vishvananda/netlink/conntrack_unspecified.go index 0bfdf422d1..0049048dc3 100644 --- a/vendor/github.com/vishvananda/netlink/conntrack_unspecified.go +++ b/vendor/github.com/vishvananda/netlink/conntrack_unspecified.go @@ -33,7 +33,7 @@ func ConntrackTableFlush(table ConntrackTableType) error { // ConntrackDeleteFilter deletes entries on the specified table on the base of the filter // conntrack -D [table] parameters Delete conntrack or expectation // -// Deprecated: use [ConntrackDeleteFilter] instead. +// Deprecated: use [ConntrackDeleteFilters] instead. func ConntrackDeleteFilter(table ConntrackTableType, family InetFamily, filter *ConntrackFilter) (uint, error) { return 0, ErrNotImplemented } diff --git a/vendor/github.com/vishvananda/netlink/devlink_linux.go b/vendor/github.com/vishvananda/netlink/devlink_linux.go index d98801dbbe..45d8ee4b6b 100644 --- a/vendor/github.com/vishvananda/netlink/devlink_linux.go +++ b/vendor/github.com/vishvananda/netlink/devlink_linux.go @@ -1,6 +1,7 @@ package netlink import ( + "errors" "fmt" "net" "strings" @@ -466,6 +467,8 @@ func (h *Handle) getEswitchAttrs(family *GenlFamily, dev *DevlinkDevice) { // DevLinkGetDeviceList provides a pointer to devlink devices and nil error, // otherwise returns an error code. +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) DevLinkGetDeviceList() ([]*DevlinkDevice, error) { f, err := h.GenlFamilyGet(nl.GENL_DEVLINK_NAME) if err != nil { @@ -478,9 +481,9 @@ func (h *Handle) DevLinkGetDeviceList() ([]*DevlinkDevice, error) { req := h.newNetlinkRequest(int(f.ID), unix.NLM_F_REQUEST|unix.NLM_F_ACK|unix.NLM_F_DUMP) req.AddData(msg) - msgs, err := req.Execute(unix.NETLINK_GENERIC, 0) - if err != nil { - return nil, err + msgs, executeErr := req.Execute(unix.NETLINK_GENERIC, 0) + if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { + return nil, executeErr } devices, err := parseDevLinkDeviceList(msgs) if err != nil { @@ -489,11 +492,14 @@ func (h *Handle) DevLinkGetDeviceList() ([]*DevlinkDevice, error) { for _, d := range devices { h.getEswitchAttrs(f, d) } - return devices, nil + return devices, executeErr } // DevLinkGetDeviceList provides a pointer to devlink devices and nil error, // otherwise returns an error code. +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func DevLinkGetDeviceList() ([]*DevlinkDevice, error) { return pkgHandle.DevLinkGetDeviceList() } @@ -646,6 +652,8 @@ func parseDevLinkAllPortList(msgs [][]byte) ([]*DevlinkPort, error) { // DevLinkGetPortList provides a pointer to devlink ports and nil error, // otherwise returns an error code. +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) DevLinkGetAllPortList() ([]*DevlinkPort, error) { f, err := h.GenlFamilyGet(nl.GENL_DEVLINK_NAME) if err != nil { @@ -658,19 +666,21 @@ func (h *Handle) DevLinkGetAllPortList() ([]*DevlinkPort, error) { req := h.newNetlinkRequest(int(f.ID), unix.NLM_F_REQUEST|unix.NLM_F_ACK|unix.NLM_F_DUMP) req.AddData(msg) - msgs, err := req.Execute(unix.NETLINK_GENERIC, 0) - if err != nil { - return nil, err + msgs, executeErr := req.Execute(unix.NETLINK_GENERIC, 0) + if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { + return nil, executeErr } ports, err := parseDevLinkAllPortList(msgs) if err != nil { return nil, err } - return ports, nil + return ports, executeErr } // DevLinkGetPortList provides a pointer to devlink ports and nil error, // otherwise returns an error code. +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func DevLinkGetAllPortList() ([]*DevlinkPort, error) { return pkgHandle.DevLinkGetAllPortList() } @@ -738,15 +748,18 @@ func (h *Handle) DevlinkGetDeviceResources(bus string, device string) (*DevlinkR // DevlinkGetDeviceParams returns parameters for devlink device // Equivalent to: `devlink dev param show /` +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) DevlinkGetDeviceParams(bus string, device string) ([]*DevlinkParam, error) { _, req, err := h.createCmdReq(nl.DEVLINK_CMD_PARAM_GET, bus, device) if err != nil { return nil, err } req.Flags |= unix.NLM_F_DUMP - respmsg, err := req.Execute(unix.NETLINK_GENERIC, 0) - if err != nil { - return nil, err + respmsg, executeErr := req.Execute(unix.NETLINK_GENERIC, 0) + if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { + return nil, executeErr } var params []*DevlinkParam for _, m := range respmsg { @@ -761,11 +774,14 @@ func (h *Handle) DevlinkGetDeviceParams(bus string, device string) ([]*DevlinkPa params = append(params, p) } - return params, nil + return params, executeErr } // DevlinkGetDeviceParams returns parameters for devlink device // Equivalent to: `devlink dev param show /` +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func DevlinkGetDeviceParams(bus string, device string) ([]*DevlinkParam, error) { return pkgHandle.DevlinkGetDeviceParams(bus, device) } diff --git a/vendor/github.com/vishvananda/netlink/filter_linux.go b/vendor/github.com/vishvananda/netlink/filter_linux.go index 87cd18f8e4..19306612ee 100644 --- a/vendor/github.com/vishvananda/netlink/filter_linux.go +++ b/vendor/github.com/vishvananda/netlink/filter_linux.go @@ -405,14 +405,20 @@ func (h *Handle) filterModify(filter Filter, proto, flags int) error { // FilterList gets a list of filters in the system. // Equivalent to: `tc filter show`. +// // Generally returns nothing if link and parent are not specified. +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func FilterList(link Link, parent uint32) ([]Filter, error) { return pkgHandle.FilterList(link, parent) } // FilterList gets a list of filters in the system. // Equivalent to: `tc filter show`. +// // Generally returns nothing if link and parent are not specified. +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) FilterList(link Link, parent uint32) ([]Filter, error) { req := h.newNetlinkRequest(unix.RTM_GETTFILTER, unix.NLM_F_DUMP) msg := &nl.TcMsg{ @@ -426,9 +432,9 @@ func (h *Handle) FilterList(link Link, parent uint32) ([]Filter, error) { } req.AddData(msg) - msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWTFILTER) - if err != nil { - return nil, err + msgs, executeErr := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWTFILTER) + if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { + return nil, executeErr } var res []Filter @@ -516,7 +522,7 @@ func (h *Handle) FilterList(link Link, parent uint32) ([]Filter, error) { } } - return res, nil + return res, executeErr } func toTcGen(attrs *ActionAttrs, tcgen *nl.TcGen) { @@ -920,9 +926,11 @@ func parseActions(tables []syscall.NetlinkRouteAttr) ([]Action, error) { actionnStatistic = (*ActionStatistic)(s) } } - action.Attrs().Statistics = actionnStatistic - action.Attrs().Timestamp = actionTimestamp - actions = append(actions, action) + if action != nil { + action.Attrs().Statistics = actionnStatistic + action.Attrs().Timestamp = actionTimestamp + actions = append(actions, action) + } } return actions, nil } diff --git a/vendor/github.com/vishvananda/netlink/fou.go b/vendor/github.com/vishvananda/netlink/fou.go index 71e73c37a0..ea9f6cf673 100644 --- a/vendor/github.com/vishvananda/netlink/fou.go +++ b/vendor/github.com/vishvananda/netlink/fou.go @@ -1,16 +1,7 @@ package netlink import ( - "errors" -) - -var ( - // ErrAttrHeaderTruncated is returned when a netlink attribute's header is - // truncated. - ErrAttrHeaderTruncated = errors.New("attribute header truncated") - // ErrAttrBodyTruncated is returned when a netlink attribute's body is - // truncated. - ErrAttrBodyTruncated = errors.New("attribute body truncated") + "net" ) type Fou struct { @@ -18,4 +9,8 @@ type Fou struct { Port int Protocol int EncapType int + Local net.IP + Peer net.IP + PeerPort int + IfIndex int } diff --git a/vendor/github.com/vishvananda/netlink/fou_linux.go b/vendor/github.com/vishvananda/netlink/fou_linux.go index ed55b2b790..7645a5a5c2 100644 --- a/vendor/github.com/vishvananda/netlink/fou_linux.go +++ b/vendor/github.com/vishvananda/netlink/fou_linux.go @@ -1,3 +1,4 @@ +//go:build linux // +build linux package netlink @@ -5,6 +6,8 @@ package netlink import ( "encoding/binary" "errors" + "log" + "net" "github.com/vishvananda/netlink/nl" "golang.org/x/sys/unix" @@ -29,6 +32,12 @@ const ( FOU_ATTR_IPPROTO FOU_ATTR_TYPE FOU_ATTR_REMCSUM_NOPARTIAL + FOU_ATTR_LOCAL_V4 + FOU_ATTR_LOCAL_V6 + FOU_ATTR_PEER_V4 + FOU_ATTR_PEER_V6 + FOU_ATTR_PEER_PORT + FOU_ATTR_IFINDEX FOU_ATTR_MAX = FOU_ATTR_REMCSUM_NOPARTIAL ) @@ -128,10 +137,14 @@ func (h *Handle) FouDel(f Fou) error { return nil } +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func FouList(fam int) ([]Fou, error) { return pkgHandle.FouList(fam) } +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) FouList(fam int) ([]Fou, error) { fam_id, err := FouFamilyId() if err != nil { @@ -150,9 +163,9 @@ func (h *Handle) FouList(fam int) ([]Fou, error) { req.AddRawData(raw) - msgs, err := req.Execute(unix.NETLINK_GENERIC, 0) - if err != nil { - return nil, err + msgs, executeErr := req.Execute(unix.NETLINK_GENERIC, 0) + if executeErr != nil && !errors.Is(err, ErrDumpInterrupted) { + return nil, executeErr } fous := make([]Fou, 0, len(msgs)) @@ -165,45 +178,32 @@ func (h *Handle) FouList(fam int) ([]Fou, error) { fous = append(fous, f) } - return fous, nil + return fous, executeErr } func deserializeFouMsg(msg []byte) (Fou, error) { - // we'll skip to byte 4 to first attribute - msg = msg[3:] - var shift int fou := Fou{} - for { - // attribute header is at least 16 bits - if len(msg) < 4 { - return fou, ErrAttrHeaderTruncated - } - - lgt := int(binary.BigEndian.Uint16(msg[0:2])) - if len(msg) < lgt+4 { - return fou, ErrAttrBodyTruncated - } - attr := binary.BigEndian.Uint16(msg[2:4]) - - shift = lgt + 3 - switch attr { + for attr := range nl.ParseAttributes(msg[4:]) { + switch attr.Type { case FOU_ATTR_AF: - fou.Family = int(msg[5]) + fou.Family = int(attr.Value[0]) case FOU_ATTR_PORT: - fou.Port = int(binary.BigEndian.Uint16(msg[5:7])) - // port is 2 bytes - shift = lgt + 2 + fou.Port = int(networkOrder.Uint16(attr.Value)) case FOU_ATTR_IPPROTO: - fou.Protocol = int(msg[5]) + fou.Protocol = int(attr.Value[0]) case FOU_ATTR_TYPE: - fou.EncapType = int(msg[5]) - } - - msg = msg[shift:] - - if len(msg) < 4 { - break + fou.EncapType = int(attr.Value[0]) + case FOU_ATTR_LOCAL_V4, FOU_ATTR_LOCAL_V6: + fou.Local = net.IP(attr.Value) + case FOU_ATTR_PEER_V4, FOU_ATTR_PEER_V6: + fou.Peer = net.IP(attr.Value) + case FOU_ATTR_PEER_PORT: + fou.PeerPort = int(networkOrder.Uint16(attr.Value)) + case FOU_ATTR_IFINDEX: + fou.IfIndex = int(native.Uint16(attr.Value)) + default: + log.Printf("unknown fou attribute from kernel: %+v %v", attr, attr.Type&nl.NLA_TYPE_MASK) } } diff --git a/vendor/github.com/vishvananda/netlink/fou_unspecified.go b/vendor/github.com/vishvananda/netlink/fou_unspecified.go index 3a8365bfe6..7e550151ad 100644 --- a/vendor/github.com/vishvananda/netlink/fou_unspecified.go +++ b/vendor/github.com/vishvananda/netlink/fou_unspecified.go @@ -1,3 +1,4 @@ +//go:build !linux // +build !linux package netlink diff --git a/vendor/github.com/vishvananda/netlink/genetlink_linux.go b/vendor/github.com/vishvananda/netlink/genetlink_linux.go index 772e5834a2..7bdaad97b4 100644 --- a/vendor/github.com/vishvananda/netlink/genetlink_linux.go +++ b/vendor/github.com/vishvananda/netlink/genetlink_linux.go @@ -1,6 +1,7 @@ package netlink import ( + "errors" "fmt" "syscall" @@ -126,6 +127,8 @@ func parseFamilies(msgs [][]byte) ([]*GenlFamily, error) { return families, nil } +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) GenlFamilyList() ([]*GenlFamily, error) { msg := &nl.Genlmsg{ Command: nl.GENL_CTRL_CMD_GETFAMILY, @@ -133,13 +136,19 @@ func (h *Handle) GenlFamilyList() ([]*GenlFamily, error) { } req := h.newNetlinkRequest(nl.GENL_ID_CTRL, unix.NLM_F_DUMP) req.AddData(msg) - msgs, err := req.Execute(unix.NETLINK_GENERIC, 0) + msgs, executeErr := req.Execute(unix.NETLINK_GENERIC, 0) + if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { + return nil, executeErr + } + families, err := parseFamilies(msgs) if err != nil { return nil, err } - return parseFamilies(msgs) + return families, executeErr } +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func GenlFamilyList() ([]*GenlFamily, error) { return pkgHandle.GenlFamilyList() } diff --git a/vendor/github.com/vishvananda/netlink/gtp_linux.go b/vendor/github.com/vishvananda/netlink/gtp_linux.go index f5e160ba5c..377dcae5c0 100644 --- a/vendor/github.com/vishvananda/netlink/gtp_linux.go +++ b/vendor/github.com/vishvananda/netlink/gtp_linux.go @@ -1,6 +1,7 @@ package netlink import ( + "errors" "fmt" "net" "strings" @@ -74,6 +75,8 @@ func parsePDP(msgs [][]byte) ([]*PDP, error) { return pdps, nil } +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) GTPPDPList() ([]*PDP, error) { f, err := h.GenlFamilyGet(nl.GENL_GTP_NAME) if err != nil { @@ -85,13 +88,19 @@ func (h *Handle) GTPPDPList() ([]*PDP, error) { } req := h.newNetlinkRequest(int(f.ID), unix.NLM_F_DUMP) req.AddData(msg) - msgs, err := req.Execute(unix.NETLINK_GENERIC, 0) + msgs, executeErr := req.Execute(unix.NETLINK_GENERIC, 0) + if executeErr != nil && !errors.Is(err, ErrDumpInterrupted) { + return nil, executeErr + } + pdps, err := parsePDP(msgs) if err != nil { return nil, err } - return parsePDP(msgs) + return pdps, executeErr } +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func GTPPDPList() ([]*PDP, error) { return pkgHandle.GTPPDPList() } diff --git a/vendor/github.com/vishvananda/netlink/link.go b/vendor/github.com/vishvananda/netlink/link.go index f820cdb678..e09a6cfe54 100644 --- a/vendor/github.com/vishvananda/netlink/link.go +++ b/vendor/github.com/vishvananda/netlink/link.go @@ -377,6 +377,13 @@ const ( NETKIT_POLICY_BLACKHOLE NetkitPolicy = 2 ) +type NetkitScrub int + +const ( + NETKIT_SCRUB_NONE NetkitScrub = 0 + NETKIT_SCRUB_DEFAULT NetkitScrub = 1 +) + func (n *Netkit) IsPrimary() bool { return n.isPrimary } @@ -391,6 +398,9 @@ type Netkit struct { Mode NetkitMode Policy NetkitPolicy PeerPolicy NetkitPolicy + Scrub NetkitScrub + PeerScrub NetkitScrub + supportsScrub bool isPrimary bool peerLinkAttrs LinkAttrs } @@ -403,6 +413,10 @@ func (n *Netkit) Type() string { return "netkit" } +func (n *Netkit) SupportsScrub() bool { + return n.supportsScrub +} + // Veth devices must specify PeerName on create type Veth struct { LinkAttrs @@ -761,19 +775,19 @@ const ( ) var bondXmitHashPolicyToString = map[BondXmitHashPolicy]string{ - BOND_XMIT_HASH_POLICY_LAYER2: "layer2", - BOND_XMIT_HASH_POLICY_LAYER3_4: "layer3+4", - BOND_XMIT_HASH_POLICY_LAYER2_3: "layer2+3", - BOND_XMIT_HASH_POLICY_ENCAP2_3: "encap2+3", - BOND_XMIT_HASH_POLICY_ENCAP3_4: "encap3+4", + BOND_XMIT_HASH_POLICY_LAYER2: "layer2", + BOND_XMIT_HASH_POLICY_LAYER3_4: "layer3+4", + BOND_XMIT_HASH_POLICY_LAYER2_3: "layer2+3", + BOND_XMIT_HASH_POLICY_ENCAP2_3: "encap2+3", + BOND_XMIT_HASH_POLICY_ENCAP3_4: "encap3+4", BOND_XMIT_HASH_POLICY_VLAN_SRCMAC: "vlan+srcmac", } var StringToBondXmitHashPolicyMap = map[string]BondXmitHashPolicy{ - "layer2": BOND_XMIT_HASH_POLICY_LAYER2, - "layer3+4": BOND_XMIT_HASH_POLICY_LAYER3_4, - "layer2+3": BOND_XMIT_HASH_POLICY_LAYER2_3, - "encap2+3": BOND_XMIT_HASH_POLICY_ENCAP2_3, - "encap3+4": BOND_XMIT_HASH_POLICY_ENCAP3_4, + "layer2": BOND_XMIT_HASH_POLICY_LAYER2, + "layer3+4": BOND_XMIT_HASH_POLICY_LAYER3_4, + "layer2+3": BOND_XMIT_HASH_POLICY_LAYER2_3, + "encap2+3": BOND_XMIT_HASH_POLICY_ENCAP2_3, + "encap3+4": BOND_XMIT_HASH_POLICY_ENCAP3_4, "vlan+srcmac": BOND_XMIT_HASH_POLICY_VLAN_SRCMAC, } diff --git a/vendor/github.com/vishvananda/netlink/link_linux.go b/vendor/github.com/vishvananda/netlink/link_linux.go index d713612a90..52491c5804 100644 --- a/vendor/github.com/vishvananda/netlink/link_linux.go +++ b/vendor/github.com/vishvananda/netlink/link_linux.go @@ -3,6 +3,7 @@ package netlink import ( "bytes" "encoding/binary" + "errors" "fmt" "io/ioutil" "net" @@ -1807,20 +1808,20 @@ func (h *Handle) LinkDel(link Link) error { } func (h *Handle) linkByNameDump(name string) (Link, error) { - links, err := h.LinkList() - if err != nil { - return nil, err + links, executeErr := h.LinkList() + if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { + return nil, executeErr } for _, link := range links { if link.Attrs().Name == name { - return link, nil + return link, executeErr } // support finding interfaces also via altnames for _, altName := range link.Attrs().AltNames { if altName == name { - return link, nil + return link, executeErr } } } @@ -1828,25 +1829,33 @@ func (h *Handle) linkByNameDump(name string) (Link, error) { } func (h *Handle) linkByAliasDump(alias string) (Link, error) { - links, err := h.LinkList() - if err != nil { - return nil, err + links, executeErr := h.LinkList() + if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { + return nil, executeErr } for _, link := range links { if link.Attrs().Alias == alias { - return link, nil + return link, executeErr } } return nil, LinkNotFoundError{fmt.Errorf("Link alias %s not found", alias)} } // LinkByName finds a link by name and returns a pointer to the object. +// +// If the kernel doesn't support IFLA_IFNAME, this method will fall back to +// filtering a dump of all link names. In this case, if the returned error is +// [ErrDumpInterrupted] the result may be missing or outdated. func LinkByName(name string) (Link, error) { return pkgHandle.LinkByName(name) } // LinkByName finds a link by name and returns a pointer to the object. +// +// If the kernel doesn't support IFLA_IFNAME, this method will fall back to +// filtering a dump of all link names. In this case, if the returned error is +// [ErrDumpInterrupted] the result may be missing or outdated. func (h *Handle) LinkByName(name string) (Link, error) { if h.lookupByDump { return h.linkByNameDump(name) @@ -1879,12 +1888,20 @@ func (h *Handle) LinkByName(name string) (Link, error) { // LinkByAlias finds a link by its alias and returns a pointer to the object. // If there are multiple links with the alias it returns the first one +// +// If the kernel doesn't support IFLA_IFALIAS, this method will fall back to +// filtering a dump of all link names. In this case, if the returned error is +// [ErrDumpInterrupted] the result may be missing or outdated. func LinkByAlias(alias string) (Link, error) { return pkgHandle.LinkByAlias(alias) } // LinkByAlias finds a link by its alias and returns a pointer to the object. // If there are multiple links with the alias it returns the first one +// +// If the kernel doesn't support IFLA_IFALIAS, this method will fall back to +// filtering a dump of all link names. In this case, if the returned error is +// [ErrDumpInterrupted] the result may be missing or outdated. func (h *Handle) LinkByAlias(alias string) (Link, error) { if h.lookupByDump { return h.linkByAliasDump(alias) @@ -2321,6 +2338,9 @@ func LinkList() ([]Link, error) { // LinkList gets a list of link devices. // Equivalent to: `ip link show` +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) LinkList() ([]Link, error) { // NOTE(vish): This duplicates functionality in net/iface_linux.go, but we need // to get the message ourselves to parse link type. @@ -2331,9 +2351,9 @@ func (h *Handle) LinkList() ([]Link, error) { attr := nl.NewRtAttr(unix.IFLA_EXT_MASK, nl.Uint32Attr(nl.RTEXT_FILTER_VF)) req.AddData(attr) - msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWLINK) - if err != nil { - return nil, err + msgs, executeErr := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWLINK) + if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { + return nil, executeErr } var res []Link @@ -2345,7 +2365,7 @@ func (h *Handle) LinkList() ([]Link, error) { res = append(res, link) } - return res, nil + return res, executeErr } // LinkUpdate is used to pass information back from LinkSubscribe() @@ -2381,6 +2401,10 @@ type LinkSubscribeOptions struct { // LinkSubscribeWithOptions work like LinkSubscribe but enable to // provide additional options to modify the behavior. Currently, the // namespace can be provided as well as an error callback. +// +// When options.ListExisting is true, options.ErrorCallback may be +// called with [ErrDumpInterrupted] to indicate that results from +// the initial dump of links may be inconsistent or incomplete. func LinkSubscribeWithOptions(ch chan<- LinkUpdate, done <-chan struct{}, options LinkSubscribeOptions) error { if options.Namespace == nil { none := netns.None() @@ -2440,6 +2464,9 @@ func linkSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- LinkUpdate, done <-c continue } for _, m := range msgs { + if m.Header.Flags&unix.NLM_F_DUMP_INTR != 0 && cberr != nil { + cberr(ErrDumpInterrupted) + } if m.Header.Type == unix.NLMSG_DONE { continue } @@ -2649,6 +2676,8 @@ func addNetkitAttrs(nk *Netkit, linkInfo *nl.RtAttr, flag int) error { data.AddRtAttr(nl.IFLA_NETKIT_MODE, nl.Uint32Attr(uint32(nk.Mode))) data.AddRtAttr(nl.IFLA_NETKIT_POLICY, nl.Uint32Attr(uint32(nk.Policy))) data.AddRtAttr(nl.IFLA_NETKIT_PEER_POLICY, nl.Uint32Attr(uint32(nk.PeerPolicy))) + data.AddRtAttr(nl.IFLA_NETKIT_SCRUB, nl.Uint32Attr(uint32(nk.Scrub))) + data.AddRtAttr(nl.IFLA_NETKIT_PEER_SCRUB, nl.Uint32Attr(uint32(nk.PeerScrub))) if (flag & unix.NLM_F_EXCL) == 0 { // Modifying peer link attributes will not take effect @@ -2709,6 +2738,12 @@ func parseNetkitData(link Link, data []syscall.NetlinkRouteAttr) { netkit.Policy = NetkitPolicy(native.Uint32(datum.Value[0:4])) case nl.IFLA_NETKIT_PEER_POLICY: netkit.PeerPolicy = NetkitPolicy(native.Uint32(datum.Value[0:4])) + case nl.IFLA_NETKIT_SCRUB: + netkit.supportsScrub = true + netkit.Scrub = NetkitScrub(native.Uint32(datum.Value[0:4])) + case nl.IFLA_NETKIT_PEER_SCRUB: + netkit.supportsScrub = true + netkit.PeerScrub = NetkitScrub(native.Uint32(datum.Value[0:4])) } } } diff --git a/vendor/github.com/vishvananda/netlink/neigh_linux.go b/vendor/github.com/vishvananda/netlink/neigh_linux.go index 2d93044a6e..1c6f2958ae 100644 --- a/vendor/github.com/vishvananda/netlink/neigh_linux.go +++ b/vendor/github.com/vishvananda/netlink/neigh_linux.go @@ -1,6 +1,7 @@ package netlink import ( + "errors" "fmt" "net" "syscall" @@ -206,6 +207,9 @@ func neighHandle(neigh *Neigh, req *nl.NetlinkRequest) error { // NeighList returns a list of IP-MAC mappings in the system (ARP table). // Equivalent to: `ip neighbor show`. // The list can be filtered by link and ip family. +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func NeighList(linkIndex, family int) ([]Neigh, error) { return pkgHandle.NeighList(linkIndex, family) } @@ -213,6 +217,9 @@ func NeighList(linkIndex, family int) ([]Neigh, error) { // NeighProxyList returns a list of neighbor proxies in the system. // Equivalent to: `ip neighbor show proxy`. // The list can be filtered by link and ip family. +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func NeighProxyList(linkIndex, family int) ([]Neigh, error) { return pkgHandle.NeighProxyList(linkIndex, family) } @@ -220,6 +227,9 @@ func NeighProxyList(linkIndex, family int) ([]Neigh, error) { // NeighList returns a list of IP-MAC mappings in the system (ARP table). // Equivalent to: `ip neighbor show`. // The list can be filtered by link and ip family. +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) NeighList(linkIndex, family int) ([]Neigh, error) { return h.NeighListExecute(Ndmsg{ Family: uint8(family), @@ -230,6 +240,9 @@ func (h *Handle) NeighList(linkIndex, family int) ([]Neigh, error) { // NeighProxyList returns a list of neighbor proxies in the system. // Equivalent to: `ip neighbor show proxy`. // The list can be filtered by link, ip family. +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) NeighProxyList(linkIndex, family int) ([]Neigh, error) { return h.NeighListExecute(Ndmsg{ Family: uint8(family), @@ -239,18 +252,24 @@ func (h *Handle) NeighProxyList(linkIndex, family int) ([]Neigh, error) { } // NeighListExecute returns a list of neighbour entries filtered by link, ip family, flag and state. +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func NeighListExecute(msg Ndmsg) ([]Neigh, error) { return pkgHandle.NeighListExecute(msg) } // NeighListExecute returns a list of neighbour entries filtered by link, ip family, flag and state. +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) NeighListExecute(msg Ndmsg) ([]Neigh, error) { req := h.newNetlinkRequest(unix.RTM_GETNEIGH, unix.NLM_F_DUMP) req.AddData(&msg) - msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWNEIGH) - if err != nil { - return nil, err + msgs, executeErr := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWNEIGH) + if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { + return nil, executeErr } var res []Neigh @@ -281,7 +300,7 @@ func (h *Handle) NeighListExecute(msg Ndmsg) ([]Neigh, error) { res = append(res, *neigh) } - return res, nil + return res, executeErr } func NeighDeserialize(m []byte) (*Neigh, error) { @@ -364,6 +383,10 @@ type NeighSubscribeOptions struct { // NeighSubscribeWithOptions work like NeighSubscribe but enable to // provide additional options to modify the behavior. Currently, the // namespace can be provided as well as an error callback. +// +// When options.ListExisting is true, options.ErrorCallback may be +// called with [ErrDumpInterrupted] to indicate that results from +// the initial dump of links may be inconsistent or incomplete. func NeighSubscribeWithOptions(ch chan<- NeighUpdate, done <-chan struct{}, options NeighSubscribeOptions) error { if options.Namespace == nil { none := netns.None() @@ -428,6 +451,9 @@ func neighSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- NeighUpdate, done < continue } for _, m := range msgs { + if m.Header.Flags&unix.NLM_F_DUMP_INTR != 0 && cberr != nil { + cberr(ErrDumpInterrupted) + } if m.Header.Type == unix.NLMSG_DONE { if listExisting { // This will be called after handling AF_UNSPEC diff --git a/vendor/github.com/vishvananda/netlink/netlink_linux.go b/vendor/github.com/vishvananda/netlink/netlink_linux.go index a20d293d87..7416e30510 100644 --- a/vendor/github.com/vishvananda/netlink/netlink_linux.go +++ b/vendor/github.com/vishvananda/netlink/netlink_linux.go @@ -9,3 +9,6 @@ const ( FAMILY_V6 = nl.FAMILY_V6 FAMILY_MPLS = nl.FAMILY_MPLS ) + +// ErrDumpInterrupted is an alias for [nl.ErrDumpInterrupted]. +var ErrDumpInterrupted = nl.ErrDumpInterrupted diff --git a/vendor/github.com/vishvananda/netlink/nl/link_linux.go b/vendor/github.com/vishvananda/netlink/nl/link_linux.go index 0b5be470cb..6dfa16cc28 100644 --- a/vendor/github.com/vishvananda/netlink/nl/link_linux.go +++ b/vendor/github.com/vishvananda/netlink/nl/link_linux.go @@ -38,6 +38,8 @@ const ( IFLA_NETKIT_POLICY IFLA_NETKIT_PEER_POLICY IFLA_NETKIT_MODE + IFLA_NETKIT_SCRUB + IFLA_NETKIT_PEER_SCRUB IFLA_NETKIT_MAX = IFLA_NETKIT_MODE ) diff --git a/vendor/github.com/vishvananda/netlink/nl/nl_linux.go b/vendor/github.com/vishvananda/netlink/nl/nl_linux.go index 6cecc4517a..4d2732a9e8 100644 --- a/vendor/github.com/vishvananda/netlink/nl/nl_linux.go +++ b/vendor/github.com/vishvananda/netlink/nl/nl_linux.go @@ -4,6 +4,7 @@ package nl import ( "bytes" "encoding/binary" + "errors" "fmt" "net" "os" @@ -11,6 +12,7 @@ import ( "sync" "sync/atomic" "syscall" + "time" "unsafe" "github.com/vishvananda/netns" @@ -43,6 +45,26 @@ var SocketTimeoutTv = unix.Timeval{Sec: 60, Usec: 0} // ErrorMessageReporting is the default error message reporting configuration for the new netlink sockets var EnableErrorMessageReporting bool = false +// ErrDumpInterrupted is an instance of errDumpInterrupted, used to report that +// a netlink function has set the NLM_F_DUMP_INTR flag in a response message, +// indicating that the results may be incomplete or inconsistent. +var ErrDumpInterrupted = errDumpInterrupted{} + +// errDumpInterrupted is an error type, used to report that NLM_F_DUMP_INTR was +// set in a netlink response. +type errDumpInterrupted struct{} + +func (errDumpInterrupted) Error() string { + return "results may be incomplete or inconsistent" +} + +// Before errDumpInterrupted was introduced, EINTR was returned when a netlink +// response had NLM_F_DUMP_INTR. Retain backward compatibility with code that +// may be checking for EINTR using Is. +func (e errDumpInterrupted) Is(target error) bool { + return target == unix.EINTR +} + // GetIPFamily returns the family type of a net.IP. func GetIPFamily(ip net.IP) int { if len(ip) <= net.IPv4len { @@ -492,22 +514,26 @@ func (req *NetlinkRequest) AddRawData(data []byte) { // Execute the request against the given sockType. // Returns a list of netlink messages in serialized format, optionally filtered // by resType. +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (req *NetlinkRequest) Execute(sockType int, resType uint16) ([][]byte, error) { var res [][]byte err := req.ExecuteIter(sockType, resType, func(msg []byte) bool { res = append(res, msg) return true }) - if err != nil { + if err != nil && !errors.Is(err, ErrDumpInterrupted) { return nil, err } - return res, nil + return res, err } // ExecuteIter executes the request against the given sockType. // Calls the provided callback func once for each netlink message. // If the callback returns false, it is not called again, but // the remaining messages are consumed/discarded. +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. // // Thread safety: ExecuteIter holds a lock on the socket until // it finishes iteration so the callback must not call back into @@ -559,6 +585,8 @@ func (req *NetlinkRequest) ExecuteIter(sockType int, resType uint16, f func(msg return err } + dumpIntr := false + done: for { msgs, from, err := s.Receive() @@ -580,7 +608,7 @@ done: } if m.Header.Flags&unix.NLM_F_DUMP_INTR != 0 { - return syscall.Errno(unix.EINTR) + dumpIntr = true } if m.Header.Type == unix.NLMSG_DONE || m.Header.Type == unix.NLMSG_ERROR { @@ -634,6 +662,9 @@ done: } } } + if dumpIntr { + return ErrDumpInterrupted + } return nil } @@ -656,9 +687,11 @@ func NewNetlinkRequest(proto, flags int) *NetlinkRequest { } type NetlinkSocket struct { - fd int32 - file *os.File - lsa unix.SockaddrNetlink + fd int32 + file *os.File + lsa unix.SockaddrNetlink + sendTimeout int64 // Access using atomic.Load/StoreInt64 + receiveTimeout int64 // Access using atomic.Load/StoreInt64 sync.Mutex } @@ -802,8 +835,44 @@ func (s *NetlinkSocket) GetFd() int { return int(s.fd) } +func (s *NetlinkSocket) GetTimeouts() (send, receive time.Duration) { + return time.Duration(atomic.LoadInt64(&s.sendTimeout)), + time.Duration(atomic.LoadInt64(&s.receiveTimeout)) +} + func (s *NetlinkSocket) Send(request *NetlinkRequest) error { - return unix.Sendto(int(s.fd), request.Serialize(), 0, &s.lsa) + rawConn, err := s.file.SyscallConn() + if err != nil { + return err + } + var ( + deadline time.Time + innerErr error + ) + sendTimeout := atomic.LoadInt64(&s.sendTimeout) + if sendTimeout != 0 { + deadline = time.Now().Add(time.Duration(sendTimeout)) + } + if err := s.file.SetWriteDeadline(deadline); err != nil { + return err + } + serializedReq := request.Serialize() + err = rawConn.Write(func(fd uintptr) (done bool) { + innerErr = unix.Sendto(int(s.fd), serializedReq, 0, &s.lsa) + return innerErr != unix.EWOULDBLOCK + }) + if innerErr != nil { + return innerErr + } + if err != nil { + // The timeout was previously implemented using SO_SNDTIMEO on a blocking + // socket. So, continue to return EAGAIN when the timeout is reached. + if errors.Is(err, os.ErrDeadlineExceeded) { + return unix.EAGAIN + } + return err + } + return nil } func (s *NetlinkSocket) Receive() ([]syscall.NetlinkMessage, *unix.SockaddrNetlink, error) { @@ -812,20 +881,33 @@ func (s *NetlinkSocket) Receive() ([]syscall.NetlinkMessage, *unix.SockaddrNetli return nil, nil, err } var ( + deadline time.Time fromAddr *unix.SockaddrNetlink rb [RECEIVE_BUFFER_SIZE]byte nr int from unix.Sockaddr innerErr error ) + receiveTimeout := atomic.LoadInt64(&s.receiveTimeout) + if receiveTimeout != 0 { + deadline = time.Now().Add(time.Duration(receiveTimeout)) + } + if err := s.file.SetReadDeadline(deadline); err != nil { + return nil, nil, err + } err = rawConn.Read(func(fd uintptr) (done bool) { nr, from, innerErr = unix.Recvfrom(int(fd), rb[:], 0) return innerErr != unix.EWOULDBLOCK }) if innerErr != nil { - err = innerErr + return nil, nil, innerErr } if err != nil { + // The timeout was previously implemented using SO_RCVTIMEO on a blocking + // socket. So, continue to return EAGAIN when the timeout is reached. + if errors.Is(err, os.ErrDeadlineExceeded) { + return nil, nil, unix.EAGAIN + } return nil, nil, err } fromAddr, ok := from.(*unix.SockaddrNetlink) @@ -847,16 +929,14 @@ func (s *NetlinkSocket) Receive() ([]syscall.NetlinkMessage, *unix.SockaddrNetli // SetSendTimeout allows to set a send timeout on the socket func (s *NetlinkSocket) SetSendTimeout(timeout *unix.Timeval) error { - // Set a send timeout of SOCKET_SEND_TIMEOUT, this will allow the Send to periodically unblock and avoid that a routine - // remains stuck on a send on a closed fd - return unix.SetsockoptTimeval(int(s.fd), unix.SOL_SOCKET, unix.SO_SNDTIMEO, timeout) + atomic.StoreInt64(&s.sendTimeout, timeout.Nano()) + return nil } // SetReceiveTimeout allows to set a receive timeout on the socket func (s *NetlinkSocket) SetReceiveTimeout(timeout *unix.Timeval) error { - // Set a read timeout of SOCKET_READ_TIMEOUT, this will allow the Read to periodically unblock and avoid that a routine - // remains stuck on a recvmsg on a closed fd - return unix.SetsockoptTimeval(int(s.fd), unix.SOL_SOCKET, unix.SO_RCVTIMEO, timeout) + atomic.StoreInt64(&s.receiveTimeout, timeout.Nano()) + return nil } // SetReceiveBufferSize allows to set a receive buffer size on the socket diff --git a/vendor/github.com/vishvananda/netlink/protinfo_linux.go b/vendor/github.com/vishvananda/netlink/protinfo_linux.go index 1ba25d3cd4..aa51e3b470 100644 --- a/vendor/github.com/vishvananda/netlink/protinfo_linux.go +++ b/vendor/github.com/vishvananda/netlink/protinfo_linux.go @@ -1,6 +1,7 @@ package netlink import ( + "errors" "fmt" "syscall" @@ -8,10 +9,14 @@ import ( "golang.org/x/sys/unix" ) +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func LinkGetProtinfo(link Link) (Protinfo, error) { return pkgHandle.LinkGetProtinfo(link) } +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) LinkGetProtinfo(link Link) (Protinfo, error) { base := link.Attrs() h.ensureIndex(base) @@ -19,9 +24,9 @@ func (h *Handle) LinkGetProtinfo(link Link) (Protinfo, error) { req := h.newNetlinkRequest(unix.RTM_GETLINK, unix.NLM_F_DUMP) msg := nl.NewIfInfomsg(unix.AF_BRIDGE) req.AddData(msg) - msgs, err := req.Execute(unix.NETLINK_ROUTE, 0) - if err != nil { - return pi, err + msgs, executeErr := req.Execute(unix.NETLINK_ROUTE, 0) + if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { + return pi, executeErr } for _, m := range msgs { @@ -43,7 +48,7 @@ func (h *Handle) LinkGetProtinfo(link Link) (Protinfo, error) { } pi = parseProtinfo(infos) - return pi, nil + return pi, executeErr } } return pi, fmt.Errorf("Device with index %d not found", base.Index) diff --git a/vendor/github.com/vishvananda/netlink/qdisc_linux.go b/vendor/github.com/vishvananda/netlink/qdisc_linux.go index e732ae3bd6..22cf0e5825 100644 --- a/vendor/github.com/vishvananda/netlink/qdisc_linux.go +++ b/vendor/github.com/vishvananda/netlink/qdisc_linux.go @@ -1,6 +1,7 @@ package netlink import ( + "errors" "fmt" "io/ioutil" "strconv" @@ -338,6 +339,9 @@ func qdiscPayload(req *nl.NetlinkRequest, qdisc Qdisc) error { // QdiscList gets a list of qdiscs in the system. // Equivalent to: `tc qdisc show`. // The list can be filtered by link. +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func QdiscList(link Link) ([]Qdisc, error) { return pkgHandle.QdiscList(link) } @@ -345,6 +349,9 @@ func QdiscList(link Link) ([]Qdisc, error) { // QdiscList gets a list of qdiscs in the system. // Equivalent to: `tc qdisc show`. // The list can be filtered by link. +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) QdiscList(link Link) ([]Qdisc, error) { req := h.newNetlinkRequest(unix.RTM_GETQDISC, unix.NLM_F_DUMP) index := int32(0) @@ -359,9 +366,9 @@ func (h *Handle) QdiscList(link Link) ([]Qdisc, error) { } req.AddData(msg) - msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWQDISC) - if err != nil { - return nil, err + msgs, executeErr := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWQDISC) + if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { + return nil, executeErr } var res []Qdisc @@ -497,7 +504,7 @@ func (h *Handle) QdiscList(link Link) ([]Qdisc, error) { res = append(res, qdisc) } - return res, nil + return res, executeErr } func parsePfifoFastData(qdisc Qdisc, value []byte) error { diff --git a/vendor/github.com/vishvananda/netlink/rdma_link_linux.go b/vendor/github.com/vishvananda/netlink/rdma_link_linux.go index 036399db6b..9bb7507321 100644 --- a/vendor/github.com/vishvananda/netlink/rdma_link_linux.go +++ b/vendor/github.com/vishvananda/netlink/rdma_link_linux.go @@ -3,6 +3,7 @@ package netlink import ( "bytes" "encoding/binary" + "errors" "fmt" "net" @@ -85,19 +86,25 @@ func execRdmaSetLink(req *nl.NetlinkRequest) error { // RdmaLinkList gets a list of RDMA link devices. // Equivalent to: `rdma dev show` +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func RdmaLinkList() ([]*RdmaLink, error) { return pkgHandle.RdmaLinkList() } // RdmaLinkList gets a list of RDMA link devices. // Equivalent to: `rdma dev show` +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) RdmaLinkList() ([]*RdmaLink, error) { proto := getProtoField(nl.RDMA_NL_NLDEV, nl.RDMA_NLDEV_CMD_GET) req := h.newNetlinkRequest(proto, unix.NLM_F_ACK|unix.NLM_F_DUMP) - msgs, err := req.Execute(unix.NETLINK_RDMA, 0) - if err != nil { - return nil, err + msgs, executeErr := req.Execute(unix.NETLINK_RDMA, 0) + if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { + return nil, executeErr } var res []*RdmaLink @@ -109,17 +116,23 @@ func (h *Handle) RdmaLinkList() ([]*RdmaLink, error) { res = append(res, link) } - return res, nil + return res, executeErr } // RdmaLinkByName finds a link by name and returns a pointer to the object if // found and nil error, otherwise returns error code. +// +// If the returned error is [ErrDumpInterrupted], the result may be missing or +// outdated and the caller should retry. func RdmaLinkByName(name string) (*RdmaLink, error) { return pkgHandle.RdmaLinkByName(name) } // RdmaLinkByName finds a link by name and returns a pointer to the object if // found and nil error, otherwise returns error code. +// +// If the returned error is [ErrDumpInterrupted], the result may be missing or +// outdated and the caller should retry. func (h *Handle) RdmaLinkByName(name string) (*RdmaLink, error) { links, err := h.RdmaLinkList() if err != nil { @@ -288,6 +301,8 @@ func RdmaLinkDel(name string) error { } // RdmaLinkDel deletes an rdma link. +// +// If the returned error is [ErrDumpInterrupted], the caller should retry. func (h *Handle) RdmaLinkDel(name string) error { link, err := h.RdmaLinkByName(name) if err != nil { @@ -307,6 +322,7 @@ func (h *Handle) RdmaLinkDel(name string) error { // RdmaLinkAdd adds an rdma link for the specified type to the network device. // Similar to: rdma link add NAME type TYPE netdev NETDEV +// // NAME - specifies the new name of the rdma link to add // TYPE - specifies which rdma type to use. Link types: // rxe - Soft RoCE driver diff --git a/vendor/github.com/vishvananda/netlink/route_linux.go b/vendor/github.com/vishvananda/netlink/route_linux.go index 0cd4f8363a..28a132a2f0 100644 --- a/vendor/github.com/vishvananda/netlink/route_linux.go +++ b/vendor/github.com/vishvananda/netlink/route_linux.go @@ -3,6 +3,7 @@ package netlink import ( "bytes" "encoding/binary" + "errors" "fmt" "net" "strconv" @@ -1163,6 +1164,9 @@ func (h *Handle) prepareRouteReq(route *Route, req *nl.NetlinkRequest, msg *nl.R // RouteList gets a list of routes in the system. // Equivalent to: `ip route show`. // The list can be filtered by link and ip family. +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func RouteList(link Link, family int) ([]Route, error) { return pkgHandle.RouteList(link, family) } @@ -1170,6 +1174,9 @@ func RouteList(link Link, family int) ([]Route, error) { // RouteList gets a list of routes in the system. // Equivalent to: `ip route show`. // The list can be filtered by link and ip family. +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) RouteList(link Link, family int) ([]Route, error) { routeFilter := &Route{} if link != nil { @@ -1188,6 +1195,9 @@ func RouteListFiltered(family int, filter *Route, filterMask uint64) ([]Route, e // RouteListFiltered gets a list of routes in the system filtered with specified rules. // All rules must be defined in RouteFilter struct +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) RouteListFiltered(family int, filter *Route, filterMask uint64) ([]Route, error) { var res []Route err := h.RouteListFilteredIter(family, filter, filterMask, func(route Route) (cont bool) { @@ -1202,17 +1212,22 @@ func (h *Handle) RouteListFiltered(family int, filter *Route, filterMask uint64) // RouteListFilteredIter passes each route that matches the filter to the given iterator func. Iteration continues // until all routes are loaded or the func returns false. +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func RouteListFilteredIter(family int, filter *Route, filterMask uint64, f func(Route) (cont bool)) error { return pkgHandle.RouteListFilteredIter(family, filter, filterMask, f) } +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) RouteListFilteredIter(family int, filter *Route, filterMask uint64, f func(Route) (cont bool)) error { req := h.newNetlinkRequest(unix.RTM_GETROUTE, unix.NLM_F_DUMP) rtmsg := &nl.RtMsg{} rtmsg.Family = uint8(family) var parseErr error - err := h.routeHandleIter(filter, req, rtmsg, func(m []byte) bool { + executeErr := h.routeHandleIter(filter, req, rtmsg, func(m []byte) bool { msg := nl.DeserializeRtMsg(m) if family != FAMILY_ALL && msg.Family != uint8(family) { // Ignore routes not matching requested family @@ -1270,13 +1285,13 @@ func (h *Handle) RouteListFilteredIter(family int, filter *Route, filterMask uin } return f(route) }) - if err != nil { - return err + if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { + return executeErr } if parseErr != nil { return parseErr } - return nil + return executeErr } // deserializeRoute decodes a binary netlink message into a Route struct @@ -1684,6 +1699,10 @@ type RouteSubscribeOptions struct { // RouteSubscribeWithOptions work like RouteSubscribe but enable to // provide additional options to modify the behavior. Currently, the // namespace can be provided as well as an error callback. +// +// When options.ListExisting is true, options.ErrorCallback may be +// called with [ErrDumpInterrupted] to indicate that results from +// the initial dump of links may be inconsistent or incomplete. func RouteSubscribeWithOptions(ch chan<- RouteUpdate, done <-chan struct{}, options RouteSubscribeOptions) error { if options.Namespace == nil { none := netns.None() @@ -1743,6 +1762,9 @@ func routeSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- RouteUpdate, done < continue } for _, m := range msgs { + if m.Header.Flags&unix.NLM_F_DUMP_INTR != 0 && cberr != nil { + cberr(ErrDumpInterrupted) + } if m.Header.Type == unix.NLMSG_DONE { continue } diff --git a/vendor/github.com/vishvananda/netlink/rule_linux.go b/vendor/github.com/vishvananda/netlink/rule_linux.go index ddff99cfad..dba99147b2 100644 --- a/vendor/github.com/vishvananda/netlink/rule_linux.go +++ b/vendor/github.com/vishvananda/netlink/rule_linux.go @@ -2,6 +2,7 @@ package netlink import ( "bytes" + "errors" "fmt" "net" @@ -183,12 +184,18 @@ func ruleHandle(rule *Rule, req *nl.NetlinkRequest) error { // RuleList lists rules in the system. // Equivalent to: ip rule list +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func RuleList(family int) ([]Rule, error) { return pkgHandle.RuleList(family) } // RuleList lists rules in the system. // Equivalent to: ip rule list +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) RuleList(family int) ([]Rule, error) { return h.RuleListFiltered(family, nil, 0) } @@ -196,20 +203,26 @@ func (h *Handle) RuleList(family int) ([]Rule, error) { // RuleListFiltered gets a list of rules in the system filtered by the // specified rule template `filter`. // Equivalent to: ip rule list +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func RuleListFiltered(family int, filter *Rule, filterMask uint64) ([]Rule, error) { return pkgHandle.RuleListFiltered(family, filter, filterMask) } // RuleListFiltered lists rules in the system. // Equivalent to: ip rule list +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) RuleListFiltered(family int, filter *Rule, filterMask uint64) ([]Rule, error) { req := h.newNetlinkRequest(unix.RTM_GETRULE, unix.NLM_F_DUMP|unix.NLM_F_REQUEST) msg := nl.NewIfInfomsg(family) req.AddData(msg) - msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWRULE) - if err != nil { - return nil, err + msgs, executeErr := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWRULE) + if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { + return nil, executeErr } var res = make([]Rule, 0) @@ -306,7 +319,7 @@ func (h *Handle) RuleListFiltered(family int, filter *Rule, filterMask uint64) ( res = append(res, *rule) } - return res, nil + return res, executeErr } func (pr *RulePortRange) toRtAttrData() []byte { diff --git a/vendor/github.com/vishvananda/netlink/socket_linux.go b/vendor/github.com/vishvananda/netlink/socket_linux.go index 4eb4aeafbd..82891bc2e0 100644 --- a/vendor/github.com/vishvananda/netlink/socket_linux.go +++ b/vendor/github.com/vishvananda/netlink/socket_linux.go @@ -157,6 +157,9 @@ func (u *UnixSocket) deserialize(b []byte) error { } // SocketGet returns the Socket identified by its local and remote addresses. +// +// If the returned error is [ErrDumpInterrupted], the search for a result may +// be incomplete and the caller should retry. func (h *Handle) SocketGet(local, remote net.Addr) (*Socket, error) { var protocol uint8 var localIP, remoteIP net.IP @@ -232,6 +235,9 @@ func (h *Handle) SocketGet(local, remote net.Addr) (*Socket, error) { } // SocketGet returns the Socket identified by its local and remote addresses. +// +// If the returned error is [ErrDumpInterrupted], the search for a result may +// be incomplete and the caller should retry. func SocketGet(local, remote net.Addr) (*Socket, error) { return pkgHandle.SocketGet(local, remote) } @@ -283,6 +289,9 @@ func SocketDestroy(local, remote net.Addr) error { } // SocketDiagTCPInfo requests INET_DIAG_INFO for TCP protocol for specified family type and return with extension TCP info. +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) SocketDiagTCPInfo(family uint8) ([]*InetDiagTCPInfoResp, error) { // Construct the request req := h.newNetlinkRequest(nl.SOCK_DIAG_BY_FAMILY, unix.NLM_F_DUMP) @@ -295,9 +304,9 @@ func (h *Handle) SocketDiagTCPInfo(family uint8) ([]*InetDiagTCPInfoResp, error) // Do the query and parse the result var result []*InetDiagTCPInfoResp - var err error - err = req.ExecuteIter(unix.NETLINK_INET_DIAG, nl.SOCK_DIAG_BY_FAMILY, func(msg []byte) bool { + executeErr := req.ExecuteIter(unix.NETLINK_INET_DIAG, nl.SOCK_DIAG_BY_FAMILY, func(msg []byte) bool { sockInfo := &Socket{} + var err error if err = sockInfo.deserialize(msg); err != nil { return false } @@ -315,18 +324,24 @@ func (h *Handle) SocketDiagTCPInfo(family uint8) ([]*InetDiagTCPInfoResp, error) return true }) - if err != nil { - return nil, err + if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { + return nil, executeErr } - return result, nil + return result, executeErr } // SocketDiagTCPInfo requests INET_DIAG_INFO for TCP protocol for specified family type and return with extension TCP info. +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func SocketDiagTCPInfo(family uint8) ([]*InetDiagTCPInfoResp, error) { return pkgHandle.SocketDiagTCPInfo(family) } // SocketDiagTCP requests INET_DIAG_INFO for TCP protocol for specified family type and return related socket. +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) SocketDiagTCP(family uint8) ([]*Socket, error) { // Construct the request req := h.newNetlinkRequest(nl.SOCK_DIAG_BY_FAMILY, unix.NLM_F_DUMP) @@ -339,27 +354,32 @@ func (h *Handle) SocketDiagTCP(family uint8) ([]*Socket, error) { // Do the query and parse the result var result []*Socket - var err error - err = req.ExecuteIter(unix.NETLINK_INET_DIAG, nl.SOCK_DIAG_BY_FAMILY, func(msg []byte) bool { + executeErr := req.ExecuteIter(unix.NETLINK_INET_DIAG, nl.SOCK_DIAG_BY_FAMILY, func(msg []byte) bool { sockInfo := &Socket{} - if err = sockInfo.deserialize(msg); err != nil { + if err := sockInfo.deserialize(msg); err != nil { return false } result = append(result, sockInfo) return true }) - if err != nil { - return nil, err + if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { + return nil, executeErr } - return result, nil + return result, executeErr } // SocketDiagTCP requests INET_DIAG_INFO for TCP protocol for specified family type and return related socket. +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func SocketDiagTCP(family uint8) ([]*Socket, error) { return pkgHandle.SocketDiagTCP(family) } // SocketDiagUDPInfo requests INET_DIAG_INFO for UDP protocol for specified family type and return with extension info. +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) SocketDiagUDPInfo(family uint8) ([]*InetDiagUDPInfoResp, error) { // Construct the request var extensions uint8 @@ -377,14 +397,14 @@ func (h *Handle) SocketDiagUDPInfo(family uint8) ([]*InetDiagUDPInfoResp, error) // Do the query and parse the result var result []*InetDiagUDPInfoResp - var err error - err = req.ExecuteIter(unix.NETLINK_INET_DIAG, nl.SOCK_DIAG_BY_FAMILY, func(msg []byte) bool { + executeErr := req.ExecuteIter(unix.NETLINK_INET_DIAG, nl.SOCK_DIAG_BY_FAMILY, func(msg []byte) bool { sockInfo := &Socket{} - if err = sockInfo.deserialize(msg); err != nil { + if err := sockInfo.deserialize(msg); err != nil { return false } var attrs []syscall.NetlinkRouteAttr + var err error if attrs, err = nl.ParseRouteAttr(msg[sizeofSocket:]); err != nil { return false } @@ -397,18 +417,24 @@ func (h *Handle) SocketDiagUDPInfo(family uint8) ([]*InetDiagUDPInfoResp, error) result = append(result, res) return true }) - if err != nil { - return nil, err + if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { + return nil, executeErr } - return result, nil + return result, executeErr } // SocketDiagUDPInfo requests INET_DIAG_INFO for UDP protocol for specified family type and return with extension info. +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func SocketDiagUDPInfo(family uint8) ([]*InetDiagUDPInfoResp, error) { return pkgHandle.SocketDiagUDPInfo(family) } // SocketDiagUDP requests INET_DIAG_INFO for UDP protocol for specified family type and return related socket. +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) SocketDiagUDP(family uint8) ([]*Socket, error) { // Construct the request req := h.newNetlinkRequest(nl.SOCK_DIAG_BY_FAMILY, unix.NLM_F_DUMP) @@ -421,27 +447,32 @@ func (h *Handle) SocketDiagUDP(family uint8) ([]*Socket, error) { // Do the query and parse the result var result []*Socket - var err error - err = req.ExecuteIter(unix.NETLINK_INET_DIAG, nl.SOCK_DIAG_BY_FAMILY, func(msg []byte) bool { + executeErr := req.ExecuteIter(unix.NETLINK_INET_DIAG, nl.SOCK_DIAG_BY_FAMILY, func(msg []byte) bool { sockInfo := &Socket{} - if err = sockInfo.deserialize(msg); err != nil { + if err := sockInfo.deserialize(msg); err != nil { return false } result = append(result, sockInfo) return true }) - if err != nil { - return nil, err + if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { + return nil, executeErr } - return result, nil + return result, executeErr } // SocketDiagUDP requests INET_DIAG_INFO for UDP protocol for specified family type and return related socket. +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func SocketDiagUDP(family uint8) ([]*Socket, error) { return pkgHandle.SocketDiagUDP(family) } // UnixSocketDiagInfo requests UNIX_DIAG_INFO for unix sockets and return with extension info. +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) UnixSocketDiagInfo() ([]*UnixDiagInfoResp, error) { // Construct the request var extensions uint8 @@ -456,10 +487,9 @@ func (h *Handle) UnixSocketDiagInfo() ([]*UnixDiagInfoResp, error) { }) var result []*UnixDiagInfoResp - var err error - err = req.ExecuteIter(unix.NETLINK_INET_DIAG, nl.SOCK_DIAG_BY_FAMILY, func(msg []byte) bool { + executeErr := req.ExecuteIter(unix.NETLINK_INET_DIAG, nl.SOCK_DIAG_BY_FAMILY, func(msg []byte) bool { sockInfo := &UnixSocket{} - if err = sockInfo.deserialize(msg); err != nil { + if err := sockInfo.deserialize(msg); err != nil { return false } @@ -469,6 +499,7 @@ func (h *Handle) UnixSocketDiagInfo() ([]*UnixDiagInfoResp, error) { } var attrs []syscall.NetlinkRouteAttr + var err error if attrs, err = nl.ParseRouteAttr(msg[sizeofSocket:]); err != nil { return false } @@ -480,18 +511,24 @@ func (h *Handle) UnixSocketDiagInfo() ([]*UnixDiagInfoResp, error) { result = append(result, res) return true }) - if err != nil { - return nil, err + if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { + return nil, executeErr } - return result, nil + return result, executeErr } // UnixSocketDiagInfo requests UNIX_DIAG_INFO for unix sockets and return with extension info. +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func UnixSocketDiagInfo() ([]*UnixDiagInfoResp, error) { return pkgHandle.UnixSocketDiagInfo() } // UnixSocketDiag requests UNIX_DIAG_INFO for unix sockets. +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) UnixSocketDiag() ([]*UnixSocket, error) { // Construct the request req := h.newNetlinkRequest(nl.SOCK_DIAG_BY_FAMILY, unix.NLM_F_DUMP) @@ -501,10 +538,9 @@ func (h *Handle) UnixSocketDiag() ([]*UnixSocket, error) { }) var result []*UnixSocket - var err error - err = req.ExecuteIter(unix.NETLINK_INET_DIAG, nl.SOCK_DIAG_BY_FAMILY, func(msg []byte) bool { + executeErr := req.ExecuteIter(unix.NETLINK_INET_DIAG, nl.SOCK_DIAG_BY_FAMILY, func(msg []byte) bool { sockInfo := &UnixSocket{} - if err = sockInfo.deserialize(msg); err != nil { + if err := sockInfo.deserialize(msg); err != nil { return false } @@ -514,13 +550,16 @@ func (h *Handle) UnixSocketDiag() ([]*UnixSocket, error) { } return true }) - if err != nil { - return nil, err + if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { + return nil, executeErr } - return result, nil + return result, executeErr } // UnixSocketDiag requests UNIX_DIAG_INFO for unix sockets. +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func UnixSocketDiag() ([]*UnixSocket, error) { return pkgHandle.UnixSocketDiag() } diff --git a/vendor/github.com/vishvananda/netlink/socket_xdp_linux.go b/vendor/github.com/vishvananda/netlink/socket_xdp_linux.go index 20c82f9c76..c1dd00a864 100644 --- a/vendor/github.com/vishvananda/netlink/socket_xdp_linux.go +++ b/vendor/github.com/vishvananda/netlink/socket_xdp_linux.go @@ -52,8 +52,10 @@ func (s *XDPSocket) deserialize(b []byte) error { return nil } -// XDPSocketGet returns the XDP socket identified by its inode number and/or +// SocketXDPGetInfo returns the XDP socket identified by its inode number and/or // socket cookie. Specify the cookie as SOCK_ANY_COOKIE if +// +// If the returned error is [ErrDumpInterrupted], the caller should retry. func SocketXDPGetInfo(ino uint32, cookie uint64) (*XDPDiagInfoResp, error) { // We have a problem here: dumping AF_XDP sockets currently does not support // filtering. We thus need to dump all XSKs and then only filter afterwards @@ -85,6 +87,9 @@ func SocketXDPGetInfo(ino uint32, cookie uint64) (*XDPDiagInfoResp, error) { } // SocketDiagXDP requests XDP_DIAG_INFO for XDP family sockets. +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func SocketDiagXDP() ([]*XDPDiagInfoResp, error) { var result []*XDPDiagInfoResp err := socketDiagXDPExecutor(func(m syscall.NetlinkMessage) error { @@ -105,10 +110,10 @@ func SocketDiagXDP() ([]*XDPDiagInfoResp, error) { result = append(result, res) return nil }) - if err != nil { + if err != nil && !errors.Is(err, ErrDumpInterrupted) { return nil, err } - return result, nil + return result, err } // socketDiagXDPExecutor requests XDP_DIAG_INFO for XDP family sockets. @@ -128,6 +133,7 @@ func socketDiagXDPExecutor(receiver func(syscall.NetlinkMessage) error) error { return err } + dumpIntr := false loop: for { msgs, from, err := s.Receive() @@ -142,6 +148,9 @@ loop: } for _, m := range msgs { + if m.Header.Flags&unix.NLM_F_DUMP_INTR != 0 { + dumpIntr = true + } switch m.Header.Type { case unix.NLMSG_DONE: break loop @@ -154,6 +163,9 @@ loop: } } } + if dumpIntr { + return ErrDumpInterrupted + } return nil } diff --git a/vendor/github.com/vishvananda/netlink/vdpa_linux.go b/vendor/github.com/vishvananda/netlink/vdpa_linux.go index 7c15986d0f..c14877a295 100644 --- a/vendor/github.com/vishvananda/netlink/vdpa_linux.go +++ b/vendor/github.com/vishvananda/netlink/vdpa_linux.go @@ -1,6 +1,7 @@ package netlink import ( + "errors" "fmt" "net" "syscall" @@ -118,6 +119,9 @@ func VDPADelDev(name string) error { // VDPAGetDevList returns list of VDPA devices // Equivalent to: `vdpa dev show` +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func VDPAGetDevList() ([]*VDPADev, error) { return pkgHandle.VDPAGetDevList() } @@ -130,6 +134,9 @@ func VDPAGetDevByName(name string) (*VDPADev, error) { // VDPAGetDevConfigList returns list of VDPA devices configurations // Equivalent to: `vdpa dev config show` +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func VDPAGetDevConfigList() ([]*VDPADevConfig, error) { return pkgHandle.VDPAGetDevConfigList() } @@ -148,6 +155,9 @@ func VDPAGetDevVStats(name string, queueIndex uint32) (*VDPADevVStats, error) { // VDPAGetMGMTDevList returns list of mgmt devices // Equivalent to: `vdpa mgmtdev show` +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func VDPAGetMGMTDevList() ([]*VDPAMGMTDev, error) { return pkgHandle.VDPAGetMGMTDevList() } @@ -261,9 +271,9 @@ func (h *Handle) vdpaRequest(command uint8, extraFlags int, attrs []*nl.RtAttr) req.AddData(a) } - resp, err := req.Execute(unix.NETLINK_GENERIC, 0) - if err != nil { - return nil, err + resp, executeErr := req.Execute(unix.NETLINK_GENERIC, 0) + if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { + return nil, executeErr } messages := make([]vdpaNetlinkMessage, 0, len(resp)) for _, m := range resp { @@ -273,10 +283,13 @@ func (h *Handle) vdpaRequest(command uint8, extraFlags int, attrs []*nl.RtAttr) } messages = append(messages, attrs) } - return messages, nil + return messages, executeErr } // dump all devices if dev is nil +// +// If dev is nil and the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) vdpaDevGet(dev *string) ([]*VDPADev, error) { var extraFlags int var attrs []*nl.RtAttr @@ -285,9 +298,9 @@ func (h *Handle) vdpaDevGet(dev *string) ([]*VDPADev, error) { } else { extraFlags = extraFlags | unix.NLM_F_DUMP } - messages, err := h.vdpaRequest(nl.VDPA_CMD_DEV_GET, extraFlags, attrs) - if err != nil { - return nil, err + messages, executeErr := h.vdpaRequest(nl.VDPA_CMD_DEV_GET, extraFlags, attrs) + if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { + return nil, executeErr } devs := make([]*VDPADev, 0, len(messages)) for _, m := range messages { @@ -295,10 +308,13 @@ func (h *Handle) vdpaDevGet(dev *string) ([]*VDPADev, error) { d.parseAttributes(m) devs = append(devs, d) } - return devs, nil + return devs, executeErr } // dump all devices if dev is nil +// +// If dev is nil, and the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) vdpaDevConfigGet(dev *string) ([]*VDPADevConfig, error) { var extraFlags int var attrs []*nl.RtAttr @@ -307,9 +323,9 @@ func (h *Handle) vdpaDevConfigGet(dev *string) ([]*VDPADevConfig, error) { } else { extraFlags = extraFlags | unix.NLM_F_DUMP } - messages, err := h.vdpaRequest(nl.VDPA_CMD_DEV_CONFIG_GET, extraFlags, attrs) - if err != nil { - return nil, err + messages, executeErr := h.vdpaRequest(nl.VDPA_CMD_DEV_CONFIG_GET, extraFlags, attrs) + if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { + return nil, executeErr } cfgs := make([]*VDPADevConfig, 0, len(messages)) for _, m := range messages { @@ -317,10 +333,13 @@ func (h *Handle) vdpaDevConfigGet(dev *string) ([]*VDPADevConfig, error) { cfg.parseAttributes(m) cfgs = append(cfgs, cfg) } - return cfgs, nil + return cfgs, executeErr } // dump all devices if dev is nil +// +// If dev is nil and the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) vdpaMGMTDevGet(bus, dev *string) ([]*VDPAMGMTDev, error) { var extraFlags int var attrs []*nl.RtAttr @@ -336,9 +355,9 @@ func (h *Handle) vdpaMGMTDevGet(bus, dev *string) ([]*VDPAMGMTDev, error) { } else { extraFlags = extraFlags | unix.NLM_F_DUMP } - messages, err := h.vdpaRequest(nl.VDPA_CMD_MGMTDEV_GET, extraFlags, attrs) - if err != nil { - return nil, err + messages, executeErr := h.vdpaRequest(nl.VDPA_CMD_MGMTDEV_GET, extraFlags, attrs) + if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { + return nil, executeErr } cfgs := make([]*VDPAMGMTDev, 0, len(messages)) for _, m := range messages { @@ -346,7 +365,7 @@ func (h *Handle) vdpaMGMTDevGet(bus, dev *string) ([]*VDPAMGMTDev, error) { cfg.parseAttributes(m) cfgs = append(cfgs, cfg) } - return cfgs, nil + return cfgs, executeErr } // VDPANewDev adds new VDPA device @@ -385,6 +404,9 @@ func (h *Handle) VDPADelDev(name string) error { // VDPAGetDevList returns list of VDPA devices // Equivalent to: `vdpa dev show` +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) VDPAGetDevList() ([]*VDPADev, error) { return h.vdpaDevGet(nil) } @@ -404,6 +426,9 @@ func (h *Handle) VDPAGetDevByName(name string) (*VDPADev, error) { // VDPAGetDevConfigList returns list of VDPA devices configurations // Equivalent to: `vdpa dev config show` +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) VDPAGetDevConfigList() ([]*VDPADevConfig, error) { return h.vdpaDevConfigGet(nil) } @@ -441,6 +466,9 @@ func (h *Handle) VDPAGetDevVStats(name string, queueIndex uint32) (*VDPADevVStat // VDPAGetMGMTDevList returns list of mgmt devices // Equivalent to: `vdpa mgmtdev show` +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) VDPAGetMGMTDevList() ([]*VDPAMGMTDev, error) { return h.vdpaMGMTDevGet(nil, nil) } diff --git a/vendor/github.com/vishvananda/netlink/xfrm_policy_linux.go b/vendor/github.com/vishvananda/netlink/xfrm_policy_linux.go index d526739ceb..bf143a1b13 100644 --- a/vendor/github.com/vishvananda/netlink/xfrm_policy_linux.go +++ b/vendor/github.com/vishvananda/netlink/xfrm_policy_linux.go @@ -1,6 +1,7 @@ package netlink import ( + "errors" "fmt" "net" @@ -215,6 +216,9 @@ func (h *Handle) XfrmPolicyDel(policy *XfrmPolicy) error { // XfrmPolicyList gets a list of xfrm policies in the system. // Equivalent to: `ip xfrm policy show`. // The list can be filtered by ip family. +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func XfrmPolicyList(family int) ([]XfrmPolicy, error) { return pkgHandle.XfrmPolicyList(family) } @@ -222,15 +226,18 @@ func XfrmPolicyList(family int) ([]XfrmPolicy, error) { // XfrmPolicyList gets a list of xfrm policies in the system. // Equivalent to: `ip xfrm policy show`. // The list can be filtered by ip family. +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) XfrmPolicyList(family int) ([]XfrmPolicy, error) { req := h.newNetlinkRequest(nl.XFRM_MSG_GETPOLICY, unix.NLM_F_DUMP) msg := nl.NewIfInfomsg(family) req.AddData(msg) - msgs, err := req.Execute(unix.NETLINK_XFRM, nl.XFRM_MSG_NEWPOLICY) - if err != nil { - return nil, err + msgs, executeErr := req.Execute(unix.NETLINK_XFRM, nl.XFRM_MSG_NEWPOLICY) + if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { + return nil, executeErr } var res []XfrmPolicy @@ -243,7 +250,7 @@ func (h *Handle) XfrmPolicyList(family int) ([]XfrmPolicy, error) { return nil, err } } - return res, nil + return res, executeErr } // XfrmPolicyGet gets a the policy described by the index or selector, if found. diff --git a/vendor/github.com/vishvananda/netlink/xfrm_state_linux.go b/vendor/github.com/vishvananda/netlink/xfrm_state_linux.go index 554f2498c2..2f46146514 100644 --- a/vendor/github.com/vishvananda/netlink/xfrm_state_linux.go +++ b/vendor/github.com/vishvananda/netlink/xfrm_state_linux.go @@ -1,6 +1,7 @@ package netlink import ( + "errors" "fmt" "net" "time" @@ -382,6 +383,9 @@ func (h *Handle) XfrmStateDel(state *XfrmState) error { // XfrmStateList gets a list of xfrm states in the system. // Equivalent to: `ip [-4|-6] xfrm state show`. // The list can be filtered by ip family. +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func XfrmStateList(family int) ([]XfrmState, error) { return pkgHandle.XfrmStateList(family) } @@ -389,12 +393,15 @@ func XfrmStateList(family int) ([]XfrmState, error) { // XfrmStateList gets a list of xfrm states in the system. // Equivalent to: `ip xfrm state show`. // The list can be filtered by ip family. +// +// If the returned error is [ErrDumpInterrupted], results may be inconsistent +// or incomplete. func (h *Handle) XfrmStateList(family int) ([]XfrmState, error) { req := h.newNetlinkRequest(nl.XFRM_MSG_GETSA, unix.NLM_F_DUMP) - msgs, err := req.Execute(unix.NETLINK_XFRM, nl.XFRM_MSG_NEWSA) - if err != nil { - return nil, err + msgs, executeErr := req.Execute(unix.NETLINK_XFRM, nl.XFRM_MSG_NEWSA) + if executeErr != nil && !errors.Is(executeErr, ErrDumpInterrupted) { + return nil, executeErr } var res []XfrmState @@ -407,7 +414,7 @@ func (h *Handle) XfrmStateList(family int) ([]XfrmState, error) { return nil, err } } - return res, nil + return res, executeErr } // XfrmStateGet gets the xfrm state described by the ID, if found. diff --git a/vendor/go.opentelemetry.io/otel/.golangci.yml b/vendor/go.opentelemetry.io/otel/.golangci.yml index a5f904197f..d09555506f 100644 --- a/vendor/go.opentelemetry.io/otel/.golangci.yml +++ b/vendor/go.opentelemetry.io/otel/.golangci.yml @@ -25,6 +25,7 @@ linters: - revive - staticcheck - tenv + - testifylint - typecheck - unconvert - unused @@ -302,3 +303,9 @@ linters-settings: # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#waitgroup-by-value - name: waitgroup-by-value disabled: false + testifylint: + enable-all: true + disable: + - float-compare + - go-require + - require-error diff --git a/vendor/go.opentelemetry.io/otel/CHANGELOG.md b/vendor/go.opentelemetry.io/otel/CHANGELOG.md index fb107426e7..4b361d0269 100644 --- a/vendor/go.opentelemetry.io/otel/CHANGELOG.md +++ b/vendor/go.opentelemetry.io/otel/CHANGELOG.md @@ -11,6 +11,35 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm +## [1.31.0/0.53.0/0.7.0/0.0.10] 2024-10-11 + +### Added + +- Add `go.opentelemetry.io/otel/sdk/metric/exemplar` package which includes `Exemplar`, `Filter`, `TraceBasedFilter`, `AlwaysOnFilter`, `HistogramReservoir`, `FixedSizeReservoir`, `Reservoir`, `Value` and `ValueType` types. These will be used for configuring the exemplar reservoir for the metrics sdk. (#5747, #5862) +- Add `WithExportBufferSize` option to log batch processor.(#5877) + +### Changed + +- Enable exemplars by default in `go.opentelemetry.io/otel/sdk/metric`. Exemplars can be disabled by setting `OTEL_METRICS_EXEMPLAR_FILTER=always_off` (#5778) +- `Logger.Enabled` in `go.opentelemetry.io/otel/log` now accepts a newly introduced `EnabledParameters` type instead of `Record`. (#5791) +- `FilterProcessor.Enabled` in `go.opentelemetry.io/otel/sdk/log/internal/x` now accepts `EnabledParameters` instead of `Record`. (#5791) +- The `Record` type in `go.opentelemetry.io/otel/log` is no longer comparable. (#5847) +- Performance improvements for the trace SDK `SetAttributes` method in `Span`. (#5864) +- Reduce memory allocations for the `Event` and `Link` lists in `Span`. (#5858) +- Performance improvements for the trace SDK `AddEvent`, `AddLink`, `RecordError` and `End` methods in `Span`. (#5874) + +### Deprecated + +- Deprecate all examples under `go.opentelemetry.io/otel/example` as they are moved to [Contrib repository](https://github.com/open-telemetry/opentelemetry-go-contrib/tree/main/examples). (#5854) + +### Fixed + +- The race condition for multiple `FixedSize` exemplar reservoirs identified in #5814 is resolved. (#5819) +- Fix log records duplication in case of heterogeneous resource attributes by correctly mapping each log record to it's resource and scope. (#5803) +- Fix timer channel drain to avoid hanging on Go 1.23. (#5868) +- Fix delegation for global meter providers, and panic when calling otel.SetMeterProvider. (#5827) +- Change the `reflect.TypeOf` to use a nil pointer to not allocate on the heap unless necessary. (#5827) + ## [1.30.0/0.52.0/0.6.0/0.0.9] 2024-09-09 ### Added @@ -3081,7 +3110,8 @@ It contains api and sdk for trace and meter. - CircleCI build CI manifest files. - CODEOWNERS file to track owners of this project. -[Unreleased]: https://github.com/open-telemetry/opentelemetry-go/compare/v1.30.0...HEAD +[Unreleased]: https://github.com/open-telemetry/opentelemetry-go/compare/v1.31.0...HEAD +[1.31.0/0.53.0/0.7.0/0.0.10]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.31.0 [1.30.0/0.52.0/0.6.0/0.0.9]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.30.0 [1.29.0/0.51.0/0.5.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.29.0 [1.28.0/0.50.0/0.4.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.28.0 diff --git a/vendor/go.opentelemetry.io/otel/CODEOWNERS b/vendor/go.opentelemetry.io/otel/CODEOWNERS index 5904bb7070..945a07d2b0 100644 --- a/vendor/go.opentelemetry.io/otel/CODEOWNERS +++ b/vendor/go.opentelemetry.io/otel/CODEOWNERS @@ -12,6 +12,6 @@ # https://help.github.com/en/articles/about-code-owners # -* @MrAlias @XSAM @dashpole @MadVikingGod @pellared @hanyuancheung @dmathieu +* @MrAlias @XSAM @dashpole @pellared @dmathieu -CODEOWNERS @MrAlias @MadVikingGod @pellared @dashpole @XSAM @dmathieu +CODEOWNERS @MrAlias @pellared @dashpole @XSAM @dmathieu diff --git a/vendor/go.opentelemetry.io/otel/CONTRIBUTING.md b/vendor/go.opentelemetry.io/otel/CONTRIBUTING.md index 9158072535..bb33965574 100644 --- a/vendor/go.opentelemetry.io/otel/CONTRIBUTING.md +++ b/vendor/go.opentelemetry.io/otel/CONTRIBUTING.md @@ -631,11 +631,8 @@ should be canceled. ### Approvers -- [Chester Cheung](https://github.com/hanyuancheung), Tencent - ### Maintainers -- [Aaron Clawson](https://github.com/MadVikingGod), LightStep - [Damien Mathieu](https://github.com/dmathieu), Elastic - [David Ashpole](https://github.com/dashpole), Google - [Robert Pająk](https://github.com/pellared), Splunk @@ -644,11 +641,13 @@ should be canceled. ### Emeritus -- [Liz Fong-Jones](https://github.com/lizthegrey), Honeycomb -- [Gustavo Silva Paiva](https://github.com/paivagustavo), LightStep -- [Josh MacDonald](https://github.com/jmacd), LightStep +- [Aaron Clawson](https://github.com/MadVikingGod), LightStep - [Anthony Mirabella](https://github.com/Aneurysm9), AWS +- [Chester Cheung](https://github.com/hanyuancheung), Tencent - [Evan Torrie](https://github.com/evantorrie), Yahoo +- [Gustavo Silva Paiva](https://github.com/paivagustavo), LightStep +- [Josh MacDonald](https://github.com/jmacd), LightStep +- [Liz Fong-Jones](https://github.com/lizthegrey), Honeycomb ### Become an Approver or a Maintainer diff --git a/vendor/go.opentelemetry.io/otel/Makefile b/vendor/go.opentelemetry.io/otel/Makefile index b04695b242..a1228a2124 100644 --- a/vendor/go.opentelemetry.io/otel/Makefile +++ b/vendor/go.opentelemetry.io/otel/Makefile @@ -54,9 +54,6 @@ $(TOOLS)/stringer: PACKAGE=golang.org/x/tools/cmd/stringer PORTO = $(TOOLS)/porto $(TOOLS)/porto: PACKAGE=github.com/jcchavezs/porto/cmd/porto -GOJQ = $(TOOLS)/gojq -$(TOOLS)/gojq: PACKAGE=github.com/itchyny/gojq/cmd/gojq - GOTMPL = $(TOOLS)/gotmpl $(GOTMPL): PACKAGE=go.opentelemetry.io/build-tools/gotmpl @@ -67,7 +64,7 @@ GOVULNCHECK = $(TOOLS)/govulncheck $(TOOLS)/govulncheck: PACKAGE=golang.org/x/vuln/cmd/govulncheck .PHONY: tools -tools: $(CROSSLINK) $(GOLANGCI_LINT) $(MISSPELL) $(GOCOVMERGE) $(STRINGER) $(PORTO) $(GOJQ) $(SEMCONVGEN) $(MULTIMOD) $(SEMCONVKIT) $(GOTMPL) $(GORELEASE) +tools: $(CROSSLINK) $(GOLANGCI_LINT) $(MISSPELL) $(GOCOVMERGE) $(STRINGER) $(PORTO) $(SEMCONVGEN) $(MULTIMOD) $(SEMCONVKIT) $(GOTMPL) $(GORELEASE) # Virtualized python tools via docker diff --git a/vendor/go.opentelemetry.io/otel/README.md b/vendor/go.opentelemetry.io/otel/README.md index 9a65707038..efec278905 100644 --- a/vendor/go.opentelemetry.io/otel/README.md +++ b/vendor/go.opentelemetry.io/otel/README.md @@ -89,8 +89,8 @@ If you need to extend the telemetry an instrumentation library provides or want to build your own instrumentation for your application directly you will need to use the [Go otel](https://pkg.go.dev/go.opentelemetry.io/otel) -package. The included [examples](./example/) are a good way to see some -practical uses of this process. +package. The [examples](https://github.com/open-telemetry/opentelemetry-go-contrib/tree/main/examples) +are a good way to see some practical uses of this process. ### Export diff --git a/vendor/go.opentelemetry.io/otel/RELEASING.md b/vendor/go.opentelemetry.io/otel/RELEASING.md index 59992984d4..ffa9b61258 100644 --- a/vendor/go.opentelemetry.io/otel/RELEASING.md +++ b/vendor/go.opentelemetry.io/otel/RELEASING.md @@ -111,17 +111,6 @@ It is critical you make sure the version you push upstream is correct. Finally create a Release for the new `` on GitHub. The release body should include all the release notes from the Changelog for this release. -## Verify Examples - -After releasing verify that examples build outside of the repository. - -``` -./verify_examples.sh -``` - -The script copies examples into a different directory removes any `replace` declarations in `go.mod` and builds them. -This ensures they build with the published release, not the local copy. - ## Post-Release ### Contrib Repository diff --git a/vendor/go.opentelemetry.io/otel/attribute/set.go b/vendor/go.opentelemetry.io/otel/attribute/set.go index bff9c7fdbb..6cbefceadf 100644 --- a/vendor/go.opentelemetry.io/otel/attribute/set.go +++ b/vendor/go.opentelemetry.io/otel/attribute/set.go @@ -347,45 +347,25 @@ func computeDistinct(kvs []KeyValue) Distinct { func computeDistinctFixed(kvs []KeyValue) interface{} { switch len(kvs) { case 1: - ptr := new([1]KeyValue) - copy((*ptr)[:], kvs) - return *ptr + return [1]KeyValue(kvs) case 2: - ptr := new([2]KeyValue) - copy((*ptr)[:], kvs) - return *ptr + return [2]KeyValue(kvs) case 3: - ptr := new([3]KeyValue) - copy((*ptr)[:], kvs) - return *ptr + return [3]KeyValue(kvs) case 4: - ptr := new([4]KeyValue) - copy((*ptr)[:], kvs) - return *ptr + return [4]KeyValue(kvs) case 5: - ptr := new([5]KeyValue) - copy((*ptr)[:], kvs) - return *ptr + return [5]KeyValue(kvs) case 6: - ptr := new([6]KeyValue) - copy((*ptr)[:], kvs) - return *ptr + return [6]KeyValue(kvs) case 7: - ptr := new([7]KeyValue) - copy((*ptr)[:], kvs) - return *ptr + return [7]KeyValue(kvs) case 8: - ptr := new([8]KeyValue) - copy((*ptr)[:], kvs) - return *ptr + return [8]KeyValue(kvs) case 9: - ptr := new([9]KeyValue) - copy((*ptr)[:], kvs) - return *ptr + return [9]KeyValue(kvs) case 10: - ptr := new([10]KeyValue) - copy((*ptr)[:], kvs) - return *ptr + return [10]KeyValue(kvs) default: return nil } diff --git a/vendor/go.opentelemetry.io/otel/internal/global/meter.go b/vendor/go.opentelemetry.io/otel/internal/global/meter.go index f2fc3929b1..e3db438a09 100644 --- a/vendor/go.opentelemetry.io/otel/internal/global/meter.go +++ b/vendor/go.opentelemetry.io/otel/internal/global/meter.go @@ -152,14 +152,17 @@ func (m *meter) Int64Counter(name string, options ...metric.Int64CounterOption) return m.delegate.Int64Counter(name, options...) } - i := &siCounter{name: name, opts: options} cfg := metric.NewInt64CounterConfig(options...) id := instID{ name: name, - kind: reflect.TypeOf(i), + kind: reflect.TypeOf((*siCounter)(nil)), description: cfg.Description(), unit: cfg.Unit(), } + if f, ok := m.instruments[id]; ok { + return f.(metric.Int64Counter), nil + } + i := &siCounter{name: name, opts: options} m.instruments[id] = i return i, nil } @@ -172,14 +175,17 @@ func (m *meter) Int64UpDownCounter(name string, options ...metric.Int64UpDownCou return m.delegate.Int64UpDownCounter(name, options...) } - i := &siUpDownCounter{name: name, opts: options} cfg := metric.NewInt64UpDownCounterConfig(options...) id := instID{ name: name, - kind: reflect.TypeOf(i), + kind: reflect.TypeOf((*siUpDownCounter)(nil)), description: cfg.Description(), unit: cfg.Unit(), } + if f, ok := m.instruments[id]; ok { + return f.(metric.Int64UpDownCounter), nil + } + i := &siUpDownCounter{name: name, opts: options} m.instruments[id] = i return i, nil } @@ -192,14 +198,17 @@ func (m *meter) Int64Histogram(name string, options ...metric.Int64HistogramOpti return m.delegate.Int64Histogram(name, options...) } - i := &siHistogram{name: name, opts: options} cfg := metric.NewInt64HistogramConfig(options...) id := instID{ name: name, - kind: reflect.TypeOf(i), + kind: reflect.TypeOf((*siHistogram)(nil)), description: cfg.Description(), unit: cfg.Unit(), } + if f, ok := m.instruments[id]; ok { + return f.(metric.Int64Histogram), nil + } + i := &siHistogram{name: name, opts: options} m.instruments[id] = i return i, nil } @@ -212,14 +221,17 @@ func (m *meter) Int64Gauge(name string, options ...metric.Int64GaugeOption) (met return m.delegate.Int64Gauge(name, options...) } - i := &siGauge{name: name, opts: options} cfg := metric.NewInt64GaugeConfig(options...) id := instID{ name: name, - kind: reflect.TypeOf(i), + kind: reflect.TypeOf((*siGauge)(nil)), description: cfg.Description(), unit: cfg.Unit(), } + if f, ok := m.instruments[id]; ok { + return f.(metric.Int64Gauge), nil + } + i := &siGauge{name: name, opts: options} m.instruments[id] = i return i, nil } @@ -232,14 +244,17 @@ func (m *meter) Int64ObservableCounter(name string, options ...metric.Int64Obser return m.delegate.Int64ObservableCounter(name, options...) } - i := &aiCounter{name: name, opts: options} cfg := metric.NewInt64ObservableCounterConfig(options...) id := instID{ name: name, - kind: reflect.TypeOf(i), + kind: reflect.TypeOf((*aiCounter)(nil)), description: cfg.Description(), unit: cfg.Unit(), } + if f, ok := m.instruments[id]; ok { + return f.(metric.Int64ObservableCounter), nil + } + i := &aiCounter{name: name, opts: options} m.instruments[id] = i return i, nil } @@ -252,14 +267,17 @@ func (m *meter) Int64ObservableUpDownCounter(name string, options ...metric.Int6 return m.delegate.Int64ObservableUpDownCounter(name, options...) } - i := &aiUpDownCounter{name: name, opts: options} cfg := metric.NewInt64ObservableUpDownCounterConfig(options...) id := instID{ name: name, - kind: reflect.TypeOf(i), + kind: reflect.TypeOf((*aiUpDownCounter)(nil)), description: cfg.Description(), unit: cfg.Unit(), } + if f, ok := m.instruments[id]; ok { + return f.(metric.Int64ObservableUpDownCounter), nil + } + i := &aiUpDownCounter{name: name, opts: options} m.instruments[id] = i return i, nil } @@ -272,14 +290,17 @@ func (m *meter) Int64ObservableGauge(name string, options ...metric.Int64Observa return m.delegate.Int64ObservableGauge(name, options...) } - i := &aiGauge{name: name, opts: options} cfg := metric.NewInt64ObservableGaugeConfig(options...) id := instID{ name: name, - kind: reflect.TypeOf(i), + kind: reflect.TypeOf((*aiGauge)(nil)), description: cfg.Description(), unit: cfg.Unit(), } + if f, ok := m.instruments[id]; ok { + return f.(metric.Int64ObservableGauge), nil + } + i := &aiGauge{name: name, opts: options} m.instruments[id] = i return i, nil } @@ -292,14 +313,17 @@ func (m *meter) Float64Counter(name string, options ...metric.Float64CounterOpti return m.delegate.Float64Counter(name, options...) } - i := &sfCounter{name: name, opts: options} cfg := metric.NewFloat64CounterConfig(options...) id := instID{ name: name, - kind: reflect.TypeOf(i), + kind: reflect.TypeOf((*sfCounter)(nil)), description: cfg.Description(), unit: cfg.Unit(), } + if f, ok := m.instruments[id]; ok { + return f.(metric.Float64Counter), nil + } + i := &sfCounter{name: name, opts: options} m.instruments[id] = i return i, nil } @@ -312,14 +336,17 @@ func (m *meter) Float64UpDownCounter(name string, options ...metric.Float64UpDow return m.delegate.Float64UpDownCounter(name, options...) } - i := &sfUpDownCounter{name: name, opts: options} cfg := metric.NewFloat64UpDownCounterConfig(options...) id := instID{ name: name, - kind: reflect.TypeOf(i), + kind: reflect.TypeOf((*sfUpDownCounter)(nil)), description: cfg.Description(), unit: cfg.Unit(), } + if f, ok := m.instruments[id]; ok { + return f.(metric.Float64UpDownCounter), nil + } + i := &sfUpDownCounter{name: name, opts: options} m.instruments[id] = i return i, nil } @@ -332,14 +359,17 @@ func (m *meter) Float64Histogram(name string, options ...metric.Float64Histogram return m.delegate.Float64Histogram(name, options...) } - i := &sfHistogram{name: name, opts: options} cfg := metric.NewFloat64HistogramConfig(options...) id := instID{ name: name, - kind: reflect.TypeOf(i), + kind: reflect.TypeOf((*sfHistogram)(nil)), description: cfg.Description(), unit: cfg.Unit(), } + if f, ok := m.instruments[id]; ok { + return f.(metric.Float64Histogram), nil + } + i := &sfHistogram{name: name, opts: options} m.instruments[id] = i return i, nil } @@ -352,14 +382,17 @@ func (m *meter) Float64Gauge(name string, options ...metric.Float64GaugeOption) return m.delegate.Float64Gauge(name, options...) } - i := &sfGauge{name: name, opts: options} cfg := metric.NewFloat64GaugeConfig(options...) id := instID{ name: name, - kind: reflect.TypeOf(i), + kind: reflect.TypeOf((*sfGauge)(nil)), description: cfg.Description(), unit: cfg.Unit(), } + if f, ok := m.instruments[id]; ok { + return f.(metric.Float64Gauge), nil + } + i := &sfGauge{name: name, opts: options} m.instruments[id] = i return i, nil } @@ -372,14 +405,17 @@ func (m *meter) Float64ObservableCounter(name string, options ...metric.Float64O return m.delegate.Float64ObservableCounter(name, options...) } - i := &afCounter{name: name, opts: options} cfg := metric.NewFloat64ObservableCounterConfig(options...) id := instID{ name: name, - kind: reflect.TypeOf(i), + kind: reflect.TypeOf((*afCounter)(nil)), description: cfg.Description(), unit: cfg.Unit(), } + if f, ok := m.instruments[id]; ok { + return f.(metric.Float64ObservableCounter), nil + } + i := &afCounter{name: name, opts: options} m.instruments[id] = i return i, nil } @@ -392,14 +428,17 @@ func (m *meter) Float64ObservableUpDownCounter(name string, options ...metric.Fl return m.delegate.Float64ObservableUpDownCounter(name, options...) } - i := &afUpDownCounter{name: name, opts: options} cfg := metric.NewFloat64ObservableUpDownCounterConfig(options...) id := instID{ name: name, - kind: reflect.TypeOf(i), + kind: reflect.TypeOf((*afUpDownCounter)(nil)), description: cfg.Description(), unit: cfg.Unit(), } + if f, ok := m.instruments[id]; ok { + return f.(metric.Float64ObservableUpDownCounter), nil + } + i := &afUpDownCounter{name: name, opts: options} m.instruments[id] = i return i, nil } @@ -412,14 +451,17 @@ func (m *meter) Float64ObservableGauge(name string, options ...metric.Float64Obs return m.delegate.Float64ObservableGauge(name, options...) } - i := &afGauge{name: name, opts: options} cfg := metric.NewFloat64ObservableGaugeConfig(options...) id := instID{ name: name, - kind: reflect.TypeOf(i), + kind: reflect.TypeOf((*afGauge)(nil)), description: cfg.Description(), unit: cfg.Unit(), } + if f, ok := m.instruments[id]; ok { + return f.(metric.Float64ObservableGauge), nil + } + i := &afGauge{name: name, opts: options} m.instruments[id] = i return i, nil } @@ -487,6 +529,7 @@ func (c *registration) setDelegate(m metric.Meter) { reg, err := m.RegisterCallback(c.function, insts...) if err != nil { GetErrorHandler().Handle(err) + return } c.unreg = reg.Unregister diff --git a/vendor/go.opentelemetry.io/otel/internal/rawhelpers.go b/vendor/go.opentelemetry.io/otel/internal/rawhelpers.go index 9b1da2c02b..b2fe3e41d3 100644 --- a/vendor/go.opentelemetry.io/otel/internal/rawhelpers.go +++ b/vendor/go.opentelemetry.io/otel/internal/rawhelpers.go @@ -20,7 +20,8 @@ func RawToBool(r uint64) bool { } func Int64ToRaw(i int64) uint64 { - return uint64(i) + // Assumes original was a valid int64 (overflow not checked). + return uint64(i) // nolint: gosec } func RawToInt64(r uint64) int64 { diff --git a/vendor/go.opentelemetry.io/otel/metric/instrument.go b/vendor/go.opentelemetry.io/otel/metric/instrument.go index ea52e40233..a535782e1d 100644 --- a/vendor/go.opentelemetry.io/otel/metric/instrument.go +++ b/vendor/go.opentelemetry.io/otel/metric/instrument.go @@ -351,7 +351,7 @@ func WithAttributeSet(attributes attribute.Set) MeasurementOption { // // cp := make([]attribute.KeyValue, len(attributes)) // copy(cp, attributes) -// WithAttributes(attribute.NewSet(cp...)) +// WithAttributeSet(attribute.NewSet(cp...)) // // [attribute.NewSet] may modify the passed attributes so this will make a copy // of attributes before creating a set in order to ensure this function is diff --git a/vendor/go.opentelemetry.io/otel/renovate.json b/vendor/go.opentelemetry.io/otel/renovate.json index 4d36b98cf4..0a29a2f13d 100644 --- a/vendor/go.opentelemetry.io/otel/renovate.json +++ b/vendor/go.opentelemetry.io/otel/renovate.json @@ -23,6 +23,10 @@ { "matchPackageNames": ["google.golang.org/genproto/googleapis/**"], "groupName": "googleapis" + }, + { + "matchPackageNames": ["golang.org/x/**"], + "groupName": "golang.org/x" } ] } diff --git a/vendor/go.opentelemetry.io/otel/verify_examples.sh b/vendor/go.opentelemetry.io/otel/verify_examples.sh deleted file mode 100644 index e57bf57fce..0000000000 --- a/vendor/go.opentelemetry.io/otel/verify_examples.sh +++ /dev/null @@ -1,74 +0,0 @@ -#!/bin/bash - -# Copyright The OpenTelemetry Authors -# SPDX-License-Identifier: Apache-2.0 - -set -euo pipefail - -cd $(dirname $0) -TOOLS_DIR=$(pwd)/.tools - -if [ -z "${GOPATH}" ] ; then - printf "GOPATH is not defined.\n" - exit -1 -fi - -if [ ! -d "${GOPATH}" ] ; then - printf "GOPATH ${GOPATH} is invalid \n" - exit -1 -fi - -# Pre-requisites -if ! git diff --quiet; then \ - git status - printf "\n\nError: working tree is not clean\n" - exit -1 -fi - -if [ "$(git tag --contains $(git log -1 --pretty=format:"%H"))" = "" ] ; then - printf "$(git log -1)" - printf "\n\nError: HEAD is not pointing to a tagged version" -fi - -make ${TOOLS_DIR}/gojq - -DIR_TMP="${GOPATH}/src/oteltmp/" -rm -rf $DIR_TMP -mkdir -p $DIR_TMP - -printf "Copy examples to ${DIR_TMP}\n" -cp -a ./example ${DIR_TMP} - -# Update go.mod files -printf "Update go.mod: rename module and remove replace\n" - -PACKAGE_DIRS=$(find . -mindepth 2 -type f -name 'go.mod' -exec dirname {} \; | egrep 'example' | sed 's/^\.\///' | sort) - -for dir in $PACKAGE_DIRS; do - printf " Update go.mod for $dir\n" - (cd "${DIR_TMP}/${dir}" && \ - # replaces is ("mod1" "mod2" …) - replaces=($(go mod edit -json | ${TOOLS_DIR}/gojq '.Replace[].Old.Path')) && \ - # strip double quotes - replaces=("${replaces[@]%\"}") && \ - replaces=("${replaces[@]#\"}") && \ - # make an array (-dropreplace=mod1 -dropreplace=mod2 …) - dropreplaces=("${replaces[@]/#/-dropreplace=}") && \ - go mod edit -module "oteltmp/${dir}" "${dropreplaces[@]}" && \ - go mod tidy) -done -printf "Update done:\n\n" - -# Build directories that contain main package. These directories are different than -# directories that contain go.mod files. -printf "Build examples:\n" -EXAMPLES=$(./get_main_pkgs.sh ./example) -for ex in $EXAMPLES; do - printf " Build $ex in ${DIR_TMP}/${ex}\n" - (cd "${DIR_TMP}/${ex}" && \ - go build .) -done - -# Cleanup -printf "Remove copied files.\n" -rm -rf $DIR_TMP diff --git a/vendor/go.opentelemetry.io/otel/version.go b/vendor/go.opentelemetry.io/otel/version.go index 78b40f3ed2..6d3c7b1f40 100644 --- a/vendor/go.opentelemetry.io/otel/version.go +++ b/vendor/go.opentelemetry.io/otel/version.go @@ -5,5 +5,5 @@ package otel // import "go.opentelemetry.io/otel" // Version is the current release version of OpenTelemetry in use. func Version() string { - return "1.30.0" + return "1.31.0" } diff --git a/vendor/go.opentelemetry.io/otel/versions.yaml b/vendor/go.opentelemetry.io/otel/versions.yaml index 0c32f4fc46..cdebdb5eb7 100644 --- a/vendor/go.opentelemetry.io/otel/versions.yaml +++ b/vendor/go.opentelemetry.io/otel/versions.yaml @@ -3,7 +3,7 @@ module-sets: stable-v1: - version: v1.30.0 + version: v1.31.0 modules: - go.opentelemetry.io/otel - go.opentelemetry.io/otel/bridge/opencensus @@ -29,12 +29,12 @@ module-sets: - go.opentelemetry.io/otel/sdk/metric - go.opentelemetry.io/otel/trace experimental-metrics: - version: v0.52.0 + version: v0.53.0 modules: - go.opentelemetry.io/otel/example/prometheus - go.opentelemetry.io/otel/exporters/prometheus experimental-logs: - version: v0.6.0 + version: v0.7.0 modules: - go.opentelemetry.io/otel/log - go.opentelemetry.io/otel/sdk/log @@ -42,7 +42,7 @@ module-sets: - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp - go.opentelemetry.io/otel/exporters/stdout/stdoutlog experimental-schema: - version: v0.0.9 + version: v0.0.10 modules: - go.opentelemetry.io/otel/schema excluded-modules: diff --git a/vendor/golang.org/x/net/http2/config.go b/vendor/golang.org/x/net/http2/config.go new file mode 100644 index 0000000000..de58dfb8dc --- /dev/null +++ b/vendor/golang.org/x/net/http2/config.go @@ -0,0 +1,122 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package http2 + +import ( + "math" + "net/http" + "time" +) + +// http2Config is a package-internal version of net/http.HTTP2Config. +// +// http.HTTP2Config was added in Go 1.24. +// When running with a version of net/http that includes HTTP2Config, +// we merge the configuration with the fields in Transport or Server +// to produce an http2Config. +// +// Zero valued fields in http2Config are interpreted as in the +// net/http.HTTPConfig documentation. +// +// Precedence order for reconciling configurations is: +// +// - Use the net/http.{Server,Transport}.HTTP2Config value, when non-zero. +// - Otherwise use the http2.{Server.Transport} value. +// - If the resulting value is zero or out of range, use a default. +type http2Config struct { + MaxConcurrentStreams uint32 + MaxDecoderHeaderTableSize uint32 + MaxEncoderHeaderTableSize uint32 + MaxReadFrameSize uint32 + MaxUploadBufferPerConnection int32 + MaxUploadBufferPerStream int32 + SendPingTimeout time.Duration + PingTimeout time.Duration + WriteByteTimeout time.Duration + PermitProhibitedCipherSuites bool + CountError func(errType string) +} + +// configFromServer merges configuration settings from +// net/http.Server.HTTP2Config and http2.Server. +func configFromServer(h1 *http.Server, h2 *Server) http2Config { + conf := http2Config{ + MaxConcurrentStreams: h2.MaxConcurrentStreams, + MaxEncoderHeaderTableSize: h2.MaxEncoderHeaderTableSize, + MaxDecoderHeaderTableSize: h2.MaxDecoderHeaderTableSize, + MaxReadFrameSize: h2.MaxReadFrameSize, + MaxUploadBufferPerConnection: h2.MaxUploadBufferPerConnection, + MaxUploadBufferPerStream: h2.MaxUploadBufferPerStream, + SendPingTimeout: h2.ReadIdleTimeout, + PingTimeout: h2.PingTimeout, + WriteByteTimeout: h2.WriteByteTimeout, + PermitProhibitedCipherSuites: h2.PermitProhibitedCipherSuites, + CountError: h2.CountError, + } + fillNetHTTPServerConfig(&conf, h1) + setConfigDefaults(&conf, true) + return conf +} + +// configFromServer merges configuration settings from h2 and h2.t1.HTTP2 +// (the net/http Transport). +func configFromTransport(h2 *Transport) http2Config { + conf := http2Config{ + MaxEncoderHeaderTableSize: h2.MaxEncoderHeaderTableSize, + MaxDecoderHeaderTableSize: h2.MaxDecoderHeaderTableSize, + MaxReadFrameSize: h2.MaxReadFrameSize, + SendPingTimeout: h2.ReadIdleTimeout, + PingTimeout: h2.PingTimeout, + WriteByteTimeout: h2.WriteByteTimeout, + } + + // Unlike most config fields, where out-of-range values revert to the default, + // Transport.MaxReadFrameSize clips. + if conf.MaxReadFrameSize < minMaxFrameSize { + conf.MaxReadFrameSize = minMaxFrameSize + } else if conf.MaxReadFrameSize > maxFrameSize { + conf.MaxReadFrameSize = maxFrameSize + } + + if h2.t1 != nil { + fillNetHTTPTransportConfig(&conf, h2.t1) + } + setConfigDefaults(&conf, false) + return conf +} + +func setDefault[T ~int | ~int32 | ~uint32 | ~int64](v *T, minval, maxval, defval T) { + if *v < minval || *v > maxval { + *v = defval + } +} + +func setConfigDefaults(conf *http2Config, server bool) { + setDefault(&conf.MaxConcurrentStreams, 1, math.MaxUint32, defaultMaxStreams) + setDefault(&conf.MaxEncoderHeaderTableSize, 1, math.MaxUint32, initialHeaderTableSize) + setDefault(&conf.MaxDecoderHeaderTableSize, 1, math.MaxUint32, initialHeaderTableSize) + if server { + setDefault(&conf.MaxUploadBufferPerConnection, initialWindowSize, math.MaxInt32, 1<<20) + } else { + setDefault(&conf.MaxUploadBufferPerConnection, initialWindowSize, math.MaxInt32, transportDefaultConnFlow) + } + if server { + setDefault(&conf.MaxUploadBufferPerStream, 1, math.MaxInt32, 1<<20) + } else { + setDefault(&conf.MaxUploadBufferPerStream, 1, math.MaxInt32, transportDefaultStreamFlow) + } + setDefault(&conf.MaxReadFrameSize, minMaxFrameSize, maxFrameSize, defaultMaxReadFrameSize) + setDefault(&conf.PingTimeout, 1, math.MaxInt64, 15*time.Second) +} + +// adjustHTTP1MaxHeaderSize converts a limit in bytes on the size of an HTTP/1 header +// to an HTTP/2 MAX_HEADER_LIST_SIZE value. +func adjustHTTP1MaxHeaderSize(n int64) int64 { + // http2's count is in a slightly different unit and includes 32 bytes per pair. + // So, take the net/http.Server value and pad it up a bit, assuming 10 headers. + const perFieldOverhead = 32 // per http2 spec + const typicalHeaders = 10 // conservative + return n + typicalHeaders*perFieldOverhead +} diff --git a/vendor/golang.org/x/net/http2/config_go124.go b/vendor/golang.org/x/net/http2/config_go124.go new file mode 100644 index 0000000000..e3784123c8 --- /dev/null +++ b/vendor/golang.org/x/net/http2/config_go124.go @@ -0,0 +1,61 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build go1.24 + +package http2 + +import "net/http" + +// fillNetHTTPServerConfig sets fields in conf from srv.HTTP2. +func fillNetHTTPServerConfig(conf *http2Config, srv *http.Server) { + fillNetHTTPConfig(conf, srv.HTTP2) +} + +// fillNetHTTPServerConfig sets fields in conf from tr.HTTP2. +func fillNetHTTPTransportConfig(conf *http2Config, tr *http.Transport) { + fillNetHTTPConfig(conf, tr.HTTP2) +} + +func fillNetHTTPConfig(conf *http2Config, h2 *http.HTTP2Config) { + if h2 == nil { + return + } + if h2.MaxConcurrentStreams != 0 { + conf.MaxConcurrentStreams = uint32(h2.MaxConcurrentStreams) + } + if h2.MaxEncoderHeaderTableSize != 0 { + conf.MaxEncoderHeaderTableSize = uint32(h2.MaxEncoderHeaderTableSize) + } + if h2.MaxDecoderHeaderTableSize != 0 { + conf.MaxDecoderHeaderTableSize = uint32(h2.MaxDecoderHeaderTableSize) + } + if h2.MaxConcurrentStreams != 0 { + conf.MaxConcurrentStreams = uint32(h2.MaxConcurrentStreams) + } + if h2.MaxReadFrameSize != 0 { + conf.MaxReadFrameSize = uint32(h2.MaxReadFrameSize) + } + if h2.MaxReceiveBufferPerConnection != 0 { + conf.MaxUploadBufferPerConnection = int32(h2.MaxReceiveBufferPerConnection) + } + if h2.MaxReceiveBufferPerStream != 0 { + conf.MaxUploadBufferPerStream = int32(h2.MaxReceiveBufferPerStream) + } + if h2.SendPingTimeout != 0 { + conf.SendPingTimeout = h2.SendPingTimeout + } + if h2.PingTimeout != 0 { + conf.PingTimeout = h2.PingTimeout + } + if h2.WriteByteTimeout != 0 { + conf.WriteByteTimeout = h2.WriteByteTimeout + } + if h2.PermitProhibitedCipherSuites { + conf.PermitProhibitedCipherSuites = true + } + if h2.CountError != nil { + conf.CountError = h2.CountError + } +} diff --git a/vendor/golang.org/x/net/http2/config_pre_go124.go b/vendor/golang.org/x/net/http2/config_pre_go124.go new file mode 100644 index 0000000000..060fd6c64c --- /dev/null +++ b/vendor/golang.org/x/net/http2/config_pre_go124.go @@ -0,0 +1,16 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !go1.24 + +package http2 + +import "net/http" + +// Pre-Go 1.24 fallback. +// The Server.HTTP2 and Transport.HTTP2 config fields were added in Go 1.24. + +func fillNetHTTPServerConfig(conf *http2Config, srv *http.Server) {} + +func fillNetHTTPTransportConfig(conf *http2Config, tr *http.Transport) {} diff --git a/vendor/golang.org/x/net/http2/http2.go b/vendor/golang.org/x/net/http2/http2.go index 003e649f30..7688c356b7 100644 --- a/vendor/golang.org/x/net/http2/http2.go +++ b/vendor/golang.org/x/net/http2/http2.go @@ -19,8 +19,9 @@ import ( "bufio" "context" "crypto/tls" + "errors" "fmt" - "io" + "net" "net/http" "os" "sort" @@ -237,13 +238,19 @@ func (cw closeWaiter) Wait() { // Its buffered writer is lazily allocated as needed, to minimize // idle memory usage with many connections. type bufferedWriter struct { - _ incomparable - w io.Writer // immutable - bw *bufio.Writer // non-nil when data is buffered + _ incomparable + group synctestGroupInterface // immutable + conn net.Conn // immutable + bw *bufio.Writer // non-nil when data is buffered + byteTimeout time.Duration // immutable, WriteByteTimeout } -func newBufferedWriter(w io.Writer) *bufferedWriter { - return &bufferedWriter{w: w} +func newBufferedWriter(group synctestGroupInterface, conn net.Conn, timeout time.Duration) *bufferedWriter { + return &bufferedWriter{ + group: group, + conn: conn, + byteTimeout: timeout, + } } // bufWriterPoolBufferSize is the size of bufio.Writer's @@ -270,7 +277,7 @@ func (w *bufferedWriter) Available() int { func (w *bufferedWriter) Write(p []byte) (n int, err error) { if w.bw == nil { bw := bufWriterPool.Get().(*bufio.Writer) - bw.Reset(w.w) + bw.Reset((*bufferedWriterTimeoutWriter)(w)) w.bw = bw } return w.bw.Write(p) @@ -288,6 +295,38 @@ func (w *bufferedWriter) Flush() error { return err } +type bufferedWriterTimeoutWriter bufferedWriter + +func (w *bufferedWriterTimeoutWriter) Write(p []byte) (n int, err error) { + return writeWithByteTimeout(w.group, w.conn, w.byteTimeout, p) +} + +// writeWithByteTimeout writes to conn. +// If more than timeout passes without any bytes being written to the connection, +// the write fails. +func writeWithByteTimeout(group synctestGroupInterface, conn net.Conn, timeout time.Duration, p []byte) (n int, err error) { + if timeout <= 0 { + return conn.Write(p) + } + for { + var now time.Time + if group == nil { + now = time.Now() + } else { + now = group.Now() + } + conn.SetWriteDeadline(now.Add(timeout)) + nn, err := conn.Write(p[n:]) + n += nn + if n == len(p) || nn == 0 || !errors.Is(err, os.ErrDeadlineExceeded) { + // Either we finished the write, made no progress, or hit the deadline. + // Whichever it is, we're done now. + conn.SetWriteDeadline(time.Time{}) + return n, err + } + } +} + func mustUint31(v int32) uint32 { if v < 0 || v > 2147483647 { panic("out of range") diff --git a/vendor/golang.org/x/net/http2/server.go b/vendor/golang.org/x/net/http2/server.go index 6c349f3ec6..617b4a4762 100644 --- a/vendor/golang.org/x/net/http2/server.go +++ b/vendor/golang.org/x/net/http2/server.go @@ -29,6 +29,7 @@ import ( "bufio" "bytes" "context" + "crypto/rand" "crypto/tls" "errors" "fmt" @@ -52,10 +53,14 @@ import ( ) const ( - prefaceTimeout = 10 * time.Second - firstSettingsTimeout = 2 * time.Second // should be in-flight with preface anyway - handlerChunkWriteSize = 4 << 10 - defaultMaxStreams = 250 // TODO: make this 100 as the GFE seems to? + prefaceTimeout = 10 * time.Second + firstSettingsTimeout = 2 * time.Second // should be in-flight with preface anyway + handlerChunkWriteSize = 4 << 10 + defaultMaxStreams = 250 // TODO: make this 100 as the GFE seems to? + + // maxQueuedControlFrames is the maximum number of control frames like + // SETTINGS, PING and RST_STREAM that will be queued for writing before + // the connection is closed to prevent memory exhaustion attacks. maxQueuedControlFrames = 10000 ) @@ -127,6 +132,22 @@ type Server struct { // If zero or negative, there is no timeout. IdleTimeout time.Duration + // ReadIdleTimeout is the timeout after which a health check using a ping + // frame will be carried out if no frame is received on the connection. + // If zero, no health check is performed. + ReadIdleTimeout time.Duration + + // PingTimeout is the timeout after which the connection will be closed + // if a response to a ping is not received. + // If zero, a default of 15 seconds is used. + PingTimeout time.Duration + + // WriteByteTimeout is the timeout after which a connection will be + // closed if no data can be written to it. The timeout begins when data is + // available to write, and is extended whenever any bytes are written. + // If zero or negative, there is no timeout. + WriteByteTimeout time.Duration + // MaxUploadBufferPerConnection is the size of the initial flow // control window for each connections. The HTTP/2 spec does not // allow this to be smaller than 65535 or larger than 2^32-1. @@ -189,57 +210,6 @@ func (s *Server) afterFunc(d time.Duration, f func()) timer { return timeTimer{time.AfterFunc(d, f)} } -func (s *Server) initialConnRecvWindowSize() int32 { - if s.MaxUploadBufferPerConnection >= initialWindowSize { - return s.MaxUploadBufferPerConnection - } - return 1 << 20 -} - -func (s *Server) initialStreamRecvWindowSize() int32 { - if s.MaxUploadBufferPerStream > 0 { - return s.MaxUploadBufferPerStream - } - return 1 << 20 -} - -func (s *Server) maxReadFrameSize() uint32 { - if v := s.MaxReadFrameSize; v >= minMaxFrameSize && v <= maxFrameSize { - return v - } - return defaultMaxReadFrameSize -} - -func (s *Server) maxConcurrentStreams() uint32 { - if v := s.MaxConcurrentStreams; v > 0 { - return v - } - return defaultMaxStreams -} - -func (s *Server) maxDecoderHeaderTableSize() uint32 { - if v := s.MaxDecoderHeaderTableSize; v > 0 { - return v - } - return initialHeaderTableSize -} - -func (s *Server) maxEncoderHeaderTableSize() uint32 { - if v := s.MaxEncoderHeaderTableSize; v > 0 { - return v - } - return initialHeaderTableSize -} - -// maxQueuedControlFrames is the maximum number of control frames like -// SETTINGS, PING and RST_STREAM that will be queued for writing before -// the connection is closed to prevent memory exhaustion attacks. -func (s *Server) maxQueuedControlFrames() int { - // TODO: if anybody asks, add a Server field, and remember to define the - // behavior of negative values. - return maxQueuedControlFrames -} - type serverInternalState struct { mu sync.Mutex activeConns map[*serverConn]struct{} @@ -440,13 +410,15 @@ func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverCon baseCtx, cancel := serverConnBaseContext(c, opts) defer cancel() + http1srv := opts.baseConfig() + conf := configFromServer(http1srv, s) sc := &serverConn{ srv: s, - hs: opts.baseConfig(), + hs: http1srv, conn: c, baseCtx: baseCtx, remoteAddrStr: c.RemoteAddr().String(), - bw: newBufferedWriter(c), + bw: newBufferedWriter(s.group, c, conf.WriteByteTimeout), handler: opts.handler(), streams: make(map[uint32]*stream), readFrameCh: make(chan readFrameResult), @@ -456,9 +428,12 @@ func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverCon bodyReadCh: make(chan bodyReadMsg), // buffering doesn't matter either way doneServing: make(chan struct{}), clientMaxStreams: math.MaxUint32, // Section 6.5.2: "Initially, there is no limit to this value" - advMaxStreams: s.maxConcurrentStreams(), + advMaxStreams: conf.MaxConcurrentStreams, initialStreamSendWindowSize: initialWindowSize, + initialStreamRecvWindowSize: conf.MaxUploadBufferPerStream, maxFrameSize: initialMaxFrameSize, + pingTimeout: conf.PingTimeout, + countErrorFunc: conf.CountError, serveG: newGoroutineLock(), pushEnabled: true, sawClientPreface: opts.SawClientPreface, @@ -491,15 +466,15 @@ func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverCon sc.flow.add(initialWindowSize) sc.inflow.init(initialWindowSize) sc.hpackEncoder = hpack.NewEncoder(&sc.headerWriteBuf) - sc.hpackEncoder.SetMaxDynamicTableSizeLimit(s.maxEncoderHeaderTableSize()) + sc.hpackEncoder.SetMaxDynamicTableSizeLimit(conf.MaxEncoderHeaderTableSize) fr := NewFramer(sc.bw, c) - if s.CountError != nil { - fr.countError = s.CountError + if conf.CountError != nil { + fr.countError = conf.CountError } - fr.ReadMetaHeaders = hpack.NewDecoder(s.maxDecoderHeaderTableSize(), nil) + fr.ReadMetaHeaders = hpack.NewDecoder(conf.MaxDecoderHeaderTableSize, nil) fr.MaxHeaderListSize = sc.maxHeaderListSize() - fr.SetMaxReadFrameSize(s.maxReadFrameSize()) + fr.SetMaxReadFrameSize(conf.MaxReadFrameSize) sc.framer = fr if tc, ok := c.(connectionStater); ok { @@ -532,7 +507,7 @@ func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverCon // So for now, do nothing here again. } - if !s.PermitProhibitedCipherSuites && isBadCipher(sc.tlsState.CipherSuite) { + if !conf.PermitProhibitedCipherSuites && isBadCipher(sc.tlsState.CipherSuite) { // "Endpoints MAY choose to generate a connection error // (Section 5.4.1) of type INADEQUATE_SECURITY if one of // the prohibited cipher suites are negotiated." @@ -569,7 +544,7 @@ func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverCon opts.UpgradeRequest = nil } - sc.serve() + sc.serve(conf) } func serverConnBaseContext(c net.Conn, opts *ServeConnOpts) (ctx context.Context, cancel func()) { @@ -609,6 +584,7 @@ type serverConn struct { tlsState *tls.ConnectionState // shared by all handlers, like net/http remoteAddrStr string writeSched WriteScheduler + countErrorFunc func(errType string) // Everything following is owned by the serve loop; use serveG.check(): serveG goroutineLock // used to verify funcs are on serve() @@ -628,6 +604,7 @@ type serverConn struct { streams map[uint32]*stream unstartedHandlers []unstartedHandler initialStreamSendWindowSize int32 + initialStreamRecvWindowSize int32 maxFrameSize int32 peerMaxHeaderListSize uint32 // zero means unknown (default) canonHeader map[string]string // http2-lower-case -> Go-Canonical-Case @@ -638,9 +615,14 @@ type serverConn struct { inGoAway bool // we've started to or sent GOAWAY inFrameScheduleLoop bool // whether we're in the scheduleFrameWrite loop needToSendGoAway bool // we need to schedule a GOAWAY frame write + pingSent bool + sentPingData [8]byte goAwayCode ErrCode shutdownTimer timer // nil until used idleTimer timer // nil if unused + readIdleTimeout time.Duration + pingTimeout time.Duration + readIdleTimer timer // nil if unused // Owned by the writeFrameAsync goroutine: headerWriteBuf bytes.Buffer @@ -655,11 +637,7 @@ func (sc *serverConn) maxHeaderListSize() uint32 { if n <= 0 { n = http.DefaultMaxHeaderBytes } - // http2's count is in a slightly different unit and includes 32 bytes per pair. - // So, take the net/http.Server value and pad it up a bit, assuming 10 headers. - const perFieldOverhead = 32 // per http2 spec - const typicalHeaders = 10 // conservative - return uint32(n + typicalHeaders*perFieldOverhead) + return uint32(adjustHTTP1MaxHeaderSize(int64(n))) } func (sc *serverConn) curOpenStreams() uint32 { @@ -923,7 +901,7 @@ func (sc *serverConn) notePanic() { } } -func (sc *serverConn) serve() { +func (sc *serverConn) serve(conf http2Config) { sc.serveG.check() defer sc.notePanic() defer sc.conn.Close() @@ -937,18 +915,18 @@ func (sc *serverConn) serve() { sc.writeFrame(FrameWriteRequest{ write: writeSettings{ - {SettingMaxFrameSize, sc.srv.maxReadFrameSize()}, + {SettingMaxFrameSize, conf.MaxReadFrameSize}, {SettingMaxConcurrentStreams, sc.advMaxStreams}, {SettingMaxHeaderListSize, sc.maxHeaderListSize()}, - {SettingHeaderTableSize, sc.srv.maxDecoderHeaderTableSize()}, - {SettingInitialWindowSize, uint32(sc.srv.initialStreamRecvWindowSize())}, + {SettingHeaderTableSize, conf.MaxDecoderHeaderTableSize}, + {SettingInitialWindowSize, uint32(sc.initialStreamRecvWindowSize)}, }, }) sc.unackedSettings++ // Each connection starts with initialWindowSize inflow tokens. // If a higher value is configured, we add more tokens. - if diff := sc.srv.initialConnRecvWindowSize() - initialWindowSize; diff > 0 { + if diff := conf.MaxUploadBufferPerConnection - initialWindowSize; diff > 0 { sc.sendWindowUpdate(nil, int(diff)) } @@ -968,11 +946,18 @@ func (sc *serverConn) serve() { defer sc.idleTimer.Stop() } + if conf.SendPingTimeout > 0 { + sc.readIdleTimeout = conf.SendPingTimeout + sc.readIdleTimer = sc.srv.afterFunc(conf.SendPingTimeout, sc.onReadIdleTimer) + defer sc.readIdleTimer.Stop() + } + go sc.readFrames() // closed by defer sc.conn.Close above settingsTimer := sc.srv.afterFunc(firstSettingsTimeout, sc.onSettingsTimer) defer settingsTimer.Stop() + lastFrameTime := sc.srv.now() loopNum := 0 for { loopNum++ @@ -986,6 +971,7 @@ func (sc *serverConn) serve() { case res := <-sc.wroteFrameCh: sc.wroteFrame(res) case res := <-sc.readFrameCh: + lastFrameTime = sc.srv.now() // Process any written frames before reading new frames from the client since a // written frame could have triggered a new stream to be started. if sc.writingFrameAsync { @@ -1017,6 +1003,8 @@ func (sc *serverConn) serve() { case idleTimerMsg: sc.vlogf("connection is idle") sc.goAway(ErrCodeNo) + case readIdleTimerMsg: + sc.handlePingTimer(lastFrameTime) case shutdownTimerMsg: sc.vlogf("GOAWAY close timer fired; closing conn from %v", sc.conn.RemoteAddr()) return @@ -1039,7 +1027,7 @@ func (sc *serverConn) serve() { // If the peer is causing us to generate a lot of control frames, // but not reading them from us, assume they are trying to make us // run out of memory. - if sc.queuedControlFrames > sc.srv.maxQueuedControlFrames() { + if sc.queuedControlFrames > maxQueuedControlFrames { sc.vlogf("http2: too many control frames in send queue, closing connection") return } @@ -1055,12 +1043,39 @@ func (sc *serverConn) serve() { } } +func (sc *serverConn) handlePingTimer(lastFrameReadTime time.Time) { + if sc.pingSent { + sc.vlogf("timeout waiting for PING response") + sc.conn.Close() + return + } + + pingAt := lastFrameReadTime.Add(sc.readIdleTimeout) + now := sc.srv.now() + if pingAt.After(now) { + // We received frames since arming the ping timer. + // Reset it for the next possible timeout. + sc.readIdleTimer.Reset(pingAt.Sub(now)) + return + } + + sc.pingSent = true + // Ignore crypto/rand.Read errors: It generally can't fail, and worse case if it does + // is we send a PING frame containing 0s. + _, _ = rand.Read(sc.sentPingData[:]) + sc.writeFrame(FrameWriteRequest{ + write: &writePing{data: sc.sentPingData}, + }) + sc.readIdleTimer.Reset(sc.pingTimeout) +} + type serverMessage int // Message values sent to serveMsgCh. var ( settingsTimerMsg = new(serverMessage) idleTimerMsg = new(serverMessage) + readIdleTimerMsg = new(serverMessage) shutdownTimerMsg = new(serverMessage) gracefulShutdownMsg = new(serverMessage) handlerDoneMsg = new(serverMessage) @@ -1068,6 +1083,7 @@ var ( func (sc *serverConn) onSettingsTimer() { sc.sendServeMsg(settingsTimerMsg) } func (sc *serverConn) onIdleTimer() { sc.sendServeMsg(idleTimerMsg) } +func (sc *serverConn) onReadIdleTimer() { sc.sendServeMsg(readIdleTimerMsg) } func (sc *serverConn) onShutdownTimer() { sc.sendServeMsg(shutdownTimerMsg) } func (sc *serverConn) sendServeMsg(msg interface{}) { @@ -1320,6 +1336,10 @@ func (sc *serverConn) wroteFrame(res frameWriteResult) { sc.writingFrame = false sc.writingFrameAsync = false + if res.err != nil { + sc.conn.Close() + } + wr := res.wr if writeEndsStream(wr.write) { @@ -1594,6 +1614,11 @@ func (sc *serverConn) processFrame(f Frame) error { func (sc *serverConn) processPing(f *PingFrame) error { sc.serveG.check() if f.IsAck() { + if sc.pingSent && sc.sentPingData == f.Data { + // This is a response to a PING we sent. + sc.pingSent = false + sc.readIdleTimer.Reset(sc.readIdleTimeout) + } // 6.7 PING: " An endpoint MUST NOT respond to PING frames // containing this flag." return nil @@ -2160,7 +2185,7 @@ func (sc *serverConn) newStream(id, pusherID uint32, state streamState) *stream st.cw.Init() st.flow.conn = &sc.flow // link to conn-level counter st.flow.add(sc.initialStreamSendWindowSize) - st.inflow.init(sc.srv.initialStreamRecvWindowSize()) + st.inflow.init(sc.initialStreamRecvWindowSize) if sc.hs.WriteTimeout > 0 { st.writeDeadline = sc.srv.afterFunc(sc.hs.WriteTimeout, st.onWriteTimeout) } @@ -3301,7 +3326,7 @@ func (sc *serverConn) countError(name string, err error) error { if sc == nil || sc.srv == nil { return err } - f := sc.srv.CountError + f := sc.countErrorFunc if f == nil { return err } diff --git a/vendor/golang.org/x/net/http2/transport.go b/vendor/golang.org/x/net/http2/transport.go index 61f511f97a..0c5f64aa8b 100644 --- a/vendor/golang.org/x/net/http2/transport.go +++ b/vendor/golang.org/x/net/http2/transport.go @@ -25,7 +25,6 @@ import ( "net/http" "net/http/httptrace" "net/textproto" - "os" "sort" "strconv" "strings" @@ -227,40 +226,26 @@ func (t *Transport) contextWithTimeout(ctx context.Context, d time.Duration) (co } func (t *Transport) maxHeaderListSize() uint32 { - if t.MaxHeaderListSize == 0 { + n := int64(t.MaxHeaderListSize) + if t.t1 != nil && t.t1.MaxResponseHeaderBytes != 0 { + n = t.t1.MaxResponseHeaderBytes + if n > 0 { + n = adjustHTTP1MaxHeaderSize(n) + } + } + if n <= 0 { return 10 << 20 } - if t.MaxHeaderListSize == 0xffffffff { + if n >= 0xffffffff { return 0 } - return t.MaxHeaderListSize -} - -func (t *Transport) maxFrameReadSize() uint32 { - if t.MaxReadFrameSize == 0 { - return 0 // use the default provided by the peer - } - if t.MaxReadFrameSize < minMaxFrameSize { - return minMaxFrameSize - } - if t.MaxReadFrameSize > maxFrameSize { - return maxFrameSize - } - return t.MaxReadFrameSize + return uint32(n) } func (t *Transport) disableCompression() bool { return t.DisableCompression || (t.t1 != nil && t.t1.DisableCompression) } -func (t *Transport) pingTimeout() time.Duration { - if t.PingTimeout == 0 { - return 15 * time.Second - } - return t.PingTimeout - -} - // ConfigureTransport configures a net/http HTTP/1 Transport to use HTTP/2. // It returns an error if t1 has already been HTTP/2-enabled. // @@ -370,11 +355,14 @@ type ClientConn struct { lastActive time.Time lastIdle time.Time // time last idle // Settings from peer: (also guarded by wmu) - maxFrameSize uint32 - maxConcurrentStreams uint32 - peerMaxHeaderListSize uint64 - peerMaxHeaderTableSize uint32 - initialWindowSize uint32 + maxFrameSize uint32 + maxConcurrentStreams uint32 + peerMaxHeaderListSize uint64 + peerMaxHeaderTableSize uint32 + initialWindowSize uint32 + initialStreamRecvWindowSize int32 + readIdleTimeout time.Duration + pingTimeout time.Duration // reqHeaderMu is a 1-element semaphore channel controlling access to sending new requests. // Write to reqHeaderMu to lock it, read from it to unlock. @@ -499,6 +487,7 @@ func (cs *clientStream) closeReqBodyLocked() { } type stickyErrWriter struct { + group synctestGroupInterface conn net.Conn timeout time.Duration err *error @@ -508,22 +497,9 @@ func (sew stickyErrWriter) Write(p []byte) (n int, err error) { if *sew.err != nil { return 0, *sew.err } - for { - if sew.timeout != 0 { - sew.conn.SetWriteDeadline(time.Now().Add(sew.timeout)) - } - nn, err := sew.conn.Write(p[n:]) - n += nn - if n < len(p) && nn > 0 && errors.Is(err, os.ErrDeadlineExceeded) { - // Keep extending the deadline so long as we're making progress. - continue - } - if sew.timeout != 0 { - sew.conn.SetWriteDeadline(time.Time{}) - } - *sew.err = err - return n, err - } + n, err = writeWithByteTimeout(sew.group, sew.conn, sew.timeout, p) + *sew.err = err + return n, err } // noCachedConnError is the concrete type of ErrNoCachedConn, which @@ -758,44 +734,36 @@ func (t *Transport) expectContinueTimeout() time.Duration { return t.t1.ExpectContinueTimeout } -func (t *Transport) maxDecoderHeaderTableSize() uint32 { - if v := t.MaxDecoderHeaderTableSize; v > 0 { - return v - } - return initialHeaderTableSize -} - -func (t *Transport) maxEncoderHeaderTableSize() uint32 { - if v := t.MaxEncoderHeaderTableSize; v > 0 { - return v - } - return initialHeaderTableSize -} - func (t *Transport) NewClientConn(c net.Conn) (*ClientConn, error) { return t.newClientConn(c, t.disableKeepAlives()) } func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, error) { + conf := configFromTransport(t) cc := &ClientConn{ - t: t, - tconn: c, - readerDone: make(chan struct{}), - nextStreamID: 1, - maxFrameSize: 16 << 10, // spec default - initialWindowSize: 65535, // spec default - maxConcurrentStreams: initialMaxConcurrentStreams, // "infinite", per spec. Use a smaller value until we have received server settings. - peerMaxHeaderListSize: 0xffffffffffffffff, // "infinite", per spec. Use 2^64-1 instead. - streams: make(map[uint32]*clientStream), - singleUse: singleUse, - wantSettingsAck: true, - pings: make(map[[8]byte]chan struct{}), - reqHeaderMu: make(chan struct{}, 1), - } + t: t, + tconn: c, + readerDone: make(chan struct{}), + nextStreamID: 1, + maxFrameSize: 16 << 10, // spec default + initialWindowSize: 65535, // spec default + initialStreamRecvWindowSize: conf.MaxUploadBufferPerStream, + maxConcurrentStreams: initialMaxConcurrentStreams, // "infinite", per spec. Use a smaller value until we have received server settings. + peerMaxHeaderListSize: 0xffffffffffffffff, // "infinite", per spec. Use 2^64-1 instead. + streams: make(map[uint32]*clientStream), + singleUse: singleUse, + wantSettingsAck: true, + readIdleTimeout: conf.SendPingTimeout, + pingTimeout: conf.PingTimeout, + pings: make(map[[8]byte]chan struct{}), + reqHeaderMu: make(chan struct{}, 1), + } + var group synctestGroupInterface if t.transportTestHooks != nil { t.markNewGoroutine() t.transportTestHooks.newclientconn(cc) c = cc.tconn + group = t.group } if VerboseLogs { t.vlogf("http2: Transport creating client conn %p to %v", cc, c.RemoteAddr()) @@ -807,24 +775,23 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro // TODO: adjust this writer size to account for frame size + // MTU + crypto/tls record padding. cc.bw = bufio.NewWriter(stickyErrWriter{ + group: group, conn: c, - timeout: t.WriteByteTimeout, + timeout: conf.WriteByteTimeout, err: &cc.werr, }) cc.br = bufio.NewReader(c) cc.fr = NewFramer(cc.bw, cc.br) - if t.maxFrameReadSize() != 0 { - cc.fr.SetMaxReadFrameSize(t.maxFrameReadSize()) - } + cc.fr.SetMaxReadFrameSize(conf.MaxReadFrameSize) if t.CountError != nil { cc.fr.countError = t.CountError } - maxHeaderTableSize := t.maxDecoderHeaderTableSize() + maxHeaderTableSize := conf.MaxDecoderHeaderTableSize cc.fr.ReadMetaHeaders = hpack.NewDecoder(maxHeaderTableSize, nil) cc.fr.MaxHeaderListSize = t.maxHeaderListSize() cc.henc = hpack.NewEncoder(&cc.hbuf) - cc.henc.SetMaxDynamicTableSizeLimit(t.maxEncoderHeaderTableSize()) + cc.henc.SetMaxDynamicTableSizeLimit(conf.MaxEncoderHeaderTableSize) cc.peerMaxHeaderTableSize = initialHeaderTableSize if cs, ok := c.(connectionStater); ok { @@ -834,11 +801,9 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro initialSettings := []Setting{ {ID: SettingEnablePush, Val: 0}, - {ID: SettingInitialWindowSize, Val: transportDefaultStreamFlow}, - } - if max := t.maxFrameReadSize(); max != 0 { - initialSettings = append(initialSettings, Setting{ID: SettingMaxFrameSize, Val: max}) + {ID: SettingInitialWindowSize, Val: uint32(cc.initialStreamRecvWindowSize)}, } + initialSettings = append(initialSettings, Setting{ID: SettingMaxFrameSize, Val: conf.MaxReadFrameSize}) if max := t.maxHeaderListSize(); max != 0 { initialSettings = append(initialSettings, Setting{ID: SettingMaxHeaderListSize, Val: max}) } @@ -848,8 +813,8 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro cc.bw.Write(clientPreface) cc.fr.WriteSettings(initialSettings...) - cc.fr.WriteWindowUpdate(0, transportDefaultConnFlow) - cc.inflow.init(transportDefaultConnFlow + initialWindowSize) + cc.fr.WriteWindowUpdate(0, uint32(conf.MaxUploadBufferPerConnection)) + cc.inflow.init(conf.MaxUploadBufferPerConnection + initialWindowSize) cc.bw.Flush() if cc.werr != nil { cc.Close() @@ -867,7 +832,7 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro } func (cc *ClientConn) healthCheck() { - pingTimeout := cc.t.pingTimeout() + pingTimeout := cc.pingTimeout // We don't need to periodically ping in the health check, because the readLoop of ClientConn will // trigger the healthCheck again if there is no frame received. ctx, cancel := cc.t.contextWithTimeout(context.Background(), pingTimeout) @@ -2199,7 +2164,7 @@ type resAndError struct { func (cc *ClientConn) addStreamLocked(cs *clientStream) { cs.flow.add(int32(cc.initialWindowSize)) cs.flow.setConnFlow(&cc.flow) - cs.inflow.init(transportDefaultStreamFlow) + cs.inflow.init(cc.initialStreamRecvWindowSize) cs.ID = cc.nextStreamID cc.nextStreamID += 2 cc.streams[cs.ID] = cs @@ -2345,7 +2310,7 @@ func (cc *ClientConn) countReadFrameError(err error) { func (rl *clientConnReadLoop) run() error { cc := rl.cc gotSettings := false - readIdleTimeout := cc.t.ReadIdleTimeout + readIdleTimeout := cc.readIdleTimeout var t timer if readIdleTimeout != 0 { t = cc.t.afterFunc(readIdleTimeout, cc.healthCheck) diff --git a/vendor/golang.org/x/net/http2/write.go b/vendor/golang.org/x/net/http2/write.go index 33f61398a1..6ff6bee7e9 100644 --- a/vendor/golang.org/x/net/http2/write.go +++ b/vendor/golang.org/x/net/http2/write.go @@ -131,6 +131,16 @@ func (se StreamError) writeFrame(ctx writeContext) error { func (se StreamError) staysWithinBuffer(max int) bool { return frameHeaderLen+4 <= max } +type writePing struct { + data [8]byte +} + +func (w writePing) writeFrame(ctx writeContext) error { + return ctx.Framer().WritePing(false, w.data) +} + +func (w writePing) staysWithinBuffer(max int) bool { return frameHeaderLen+len(w.data) <= max } + type writePingAck struct{ pf *PingFrame } func (w writePingAck) writeFrame(ctx writeContext) error { diff --git a/vendor/golang.org/x/net/websocket/websocket.go b/vendor/golang.org/x/net/websocket/websocket.go index 923a5780ec..ac76165ceb 100644 --- a/vendor/golang.org/x/net/websocket/websocket.go +++ b/vendor/golang.org/x/net/websocket/websocket.go @@ -8,7 +8,7 @@ // This package currently lacks some features found in an alternative // and more actively maintained WebSocket package: // -// https://pkg.go.dev/nhooyr.io/websocket +// https://pkg.go.dev/github.com/coder/websocket package websocket // import "golang.org/x/net/websocket" import ( diff --git a/vendor/golang.org/x/oauth2/token.go b/vendor/golang.org/x/oauth2/token.go index 5bbb332174..109997d77c 100644 --- a/vendor/golang.org/x/oauth2/token.go +++ b/vendor/golang.org/x/oauth2/token.go @@ -49,6 +49,13 @@ type Token struct { // mechanisms for that TokenSource will not be used. Expiry time.Time `json:"expiry,omitempty"` + // ExpiresIn is the OAuth2 wire format "expires_in" field, + // which specifies how many seconds later the token expires, + // relative to an unknown time base approximately around "now". + // It is the application's responsibility to populate + // `Expiry` from `ExpiresIn` when required. + ExpiresIn int64 `json:"expires_in,omitempty"` + // raw optionally contains extra metadata from the server // when updating a token. raw interface{} diff --git a/vendor/golang.org/x/sys/unix/README.md b/vendor/golang.org/x/sys/unix/README.md index 7d3c060e12..6e08a76a71 100644 --- a/vendor/golang.org/x/sys/unix/README.md +++ b/vendor/golang.org/x/sys/unix/README.md @@ -156,7 +156,7 @@ from the generated architecture-specific files listed below, and merge these into a common file for each OS. The merge is performed in the following steps: -1. Construct the set of common code that is idential in all architecture-specific files. +1. Construct the set of common code that is identical in all architecture-specific files. 2. Write this common code to the merged file. 3. Remove the common code from all architecture-specific files. diff --git a/vendor/golang.org/x/sys/unix/mkerrors.sh b/vendor/golang.org/x/sys/unix/mkerrors.sh index e14b766a32..ac54ecaba0 100644 --- a/vendor/golang.org/x/sys/unix/mkerrors.sh +++ b/vendor/golang.org/x/sys/unix/mkerrors.sh @@ -656,7 +656,7 @@ errors=$( signals=$( echo '#include ' | $CC -x c - -E -dM $ccflags | awk '$1=="#define" && $2 ~ /^SIG[A-Z0-9]+$/ { print $2 }' | - grep -v 'SIGSTKSIZE\|SIGSTKSZ\|SIGRT\|SIGMAX64' | + grep -E -v '(SIGSTKSIZE|SIGSTKSZ|SIGRT|SIGMAX64)' | sort ) @@ -666,7 +666,7 @@ echo '#include ' | $CC -x c - -E -dM $ccflags | sort >_error.grep echo '#include ' | $CC -x c - -E -dM $ccflags | awk '$1=="#define" && $2 ~ /^SIG[A-Z0-9]+$/ { print "^\t" $2 "[ \t]*=" }' | - grep -v 'SIGSTKSIZE\|SIGSTKSZ\|SIGRT\|SIGMAX64' | + grep -E -v '(SIGSTKSIZE|SIGSTKSZ|SIGRT|SIGMAX64)' | sort >_signal.grep echo '// mkerrors.sh' "$@" diff --git a/vendor/golang.org/x/sys/unix/syscall_aix.go b/vendor/golang.org/x/sys/unix/syscall_aix.go index 67ce6cef2d..6f15ba1eaf 100644 --- a/vendor/golang.org/x/sys/unix/syscall_aix.go +++ b/vendor/golang.org/x/sys/unix/syscall_aix.go @@ -360,7 +360,7 @@ func Wait4(pid int, wstatus *WaitStatus, options int, rusage *Rusage) (wpid int, var status _C_int var r Pid_t err = ERESTART - // AIX wait4 may return with ERESTART errno, while the processus is still + // AIX wait4 may return with ERESTART errno, while the process is still // active. for err == ERESTART { r, err = wait4(Pid_t(pid), &status, options, rusage) diff --git a/vendor/golang.org/x/sys/unix/syscall_linux.go b/vendor/golang.org/x/sys/unix/syscall_linux.go index 3f1d3d4cb2..f08abd434f 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux.go @@ -1295,6 +1295,48 @@ func GetsockoptTCPInfo(fd, level, opt int) (*TCPInfo, error) { return &value, err } +// GetsockoptTCPCCVegasInfo returns algorithm specific congestion control information for a socket using the "vegas" +// algorithm. +// +// The socket's congestion control algorighm can be retrieved via [GetsockoptString] with the [TCP_CONGESTION] option: +// +// algo, err := unix.GetsockoptString(fd, unix.IPPROTO_TCP, unix.TCP_CONGESTION) +func GetsockoptTCPCCVegasInfo(fd, level, opt int) (*TCPVegasInfo, error) { + var value [SizeofTCPCCInfo / 4]uint32 // ensure proper alignment + vallen := _Socklen(SizeofTCPCCInfo) + err := getsockopt(fd, level, opt, unsafe.Pointer(&value[0]), &vallen) + out := (*TCPVegasInfo)(unsafe.Pointer(&value[0])) + return out, err +} + +// GetsockoptTCPCCDCTCPInfo returns algorithm specific congestion control information for a socket using the "dctp" +// algorithm. +// +// The socket's congestion control algorighm can be retrieved via [GetsockoptString] with the [TCP_CONGESTION] option: +// +// algo, err := unix.GetsockoptString(fd, unix.IPPROTO_TCP, unix.TCP_CONGESTION) +func GetsockoptTCPCCDCTCPInfo(fd, level, opt int) (*TCPDCTCPInfo, error) { + var value [SizeofTCPCCInfo / 4]uint32 // ensure proper alignment + vallen := _Socklen(SizeofTCPCCInfo) + err := getsockopt(fd, level, opt, unsafe.Pointer(&value[0]), &vallen) + out := (*TCPDCTCPInfo)(unsafe.Pointer(&value[0])) + return out, err +} + +// GetsockoptTCPCCBBRInfo returns algorithm specific congestion control information for a socket using the "bbr" +// algorithm. +// +// The socket's congestion control algorighm can be retrieved via [GetsockoptString] with the [TCP_CONGESTION] option: +// +// algo, err := unix.GetsockoptString(fd, unix.IPPROTO_TCP, unix.TCP_CONGESTION) +func GetsockoptTCPCCBBRInfo(fd, level, opt int) (*TCPBBRInfo, error) { + var value [SizeofTCPCCInfo / 4]uint32 // ensure proper alignment + vallen := _Socklen(SizeofTCPCCInfo) + err := getsockopt(fd, level, opt, unsafe.Pointer(&value[0]), &vallen) + out := (*TCPBBRInfo)(unsafe.Pointer(&value[0])) + return out, err +} + // GetsockoptString returns the string value of the socket option opt for the // socket associated with fd at the given socket level. func GetsockoptString(fd, level, opt int) (string, error) { @@ -1959,7 +2001,26 @@ func Getpgrp() (pid int) { //sysnb Getpid() (pid int) //sysnb Getppid() (ppid int) //sys Getpriority(which int, who int) (prio int, err error) -//sys Getrandom(buf []byte, flags int) (n int, err error) + +func Getrandom(buf []byte, flags int) (n int, err error) { + vdsoRet, supported := vgetrandom(buf, uint32(flags)) + if supported { + if vdsoRet < 0 { + return 0, errnoErr(syscall.Errno(-vdsoRet)) + } + return vdsoRet, nil + } + var p *byte + if len(buf) > 0 { + p = &buf[0] + } + r, _, e := Syscall(SYS_GETRANDOM, uintptr(unsafe.Pointer(p)), uintptr(len(buf)), uintptr(flags)) + if e != 0 { + return 0, errnoErr(e) + } + return int(r), nil +} + //sysnb Getrusage(who int, rusage *Rusage) (err error) //sysnb Getsid(pid int) (sid int, err error) //sysnb Gettid() (tid int) diff --git a/vendor/golang.org/x/sys/unix/syscall_linux_arm64.go b/vendor/golang.org/x/sys/unix/syscall_linux_arm64.go index cf2ee6c75e..745e5c7e6c 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux_arm64.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux_arm64.go @@ -182,3 +182,5 @@ func KexecFileLoad(kernelFd int, initrdFd int, cmdline string, flags int) error } return kexecFileLoad(kernelFd, initrdFd, cmdlineLen, cmdline, flags) } + +const SYS_FSTATAT = SYS_NEWFSTATAT diff --git a/vendor/golang.org/x/sys/unix/syscall_linux_loong64.go b/vendor/golang.org/x/sys/unix/syscall_linux_loong64.go index 3d0e98451f..dd2262a407 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux_loong64.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux_loong64.go @@ -214,3 +214,5 @@ func KexecFileLoad(kernelFd int, initrdFd int, cmdline string, flags int) error } return kexecFileLoad(kernelFd, initrdFd, cmdlineLen, cmdline, flags) } + +const SYS_FSTATAT = SYS_NEWFSTATAT diff --git a/vendor/golang.org/x/sys/unix/syscall_linux_riscv64.go b/vendor/golang.org/x/sys/unix/syscall_linux_riscv64.go index 6f5a288944..8cf3670bda 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux_riscv64.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux_riscv64.go @@ -187,3 +187,5 @@ func RISCVHWProbe(pairs []RISCVHWProbePairs, set *CPUSet, flags uint) (err error } return riscvHWProbe(pairs, setSize, set, flags) } + +const SYS_FSTATAT = SYS_NEWFSTATAT diff --git a/vendor/golang.org/x/sys/unix/vgetrandom_linux.go b/vendor/golang.org/x/sys/unix/vgetrandom_linux.go new file mode 100644 index 0000000000..07ac8e09d1 --- /dev/null +++ b/vendor/golang.org/x/sys/unix/vgetrandom_linux.go @@ -0,0 +1,13 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build linux && go1.24 + +package unix + +import _ "unsafe" + +//go:linkname vgetrandom runtime.vgetrandom +//go:noescape +func vgetrandom(p []byte, flags uint32) (ret int, supported bool) diff --git a/vendor/golang.org/x/sys/unix/vgetrandom_unsupported.go b/vendor/golang.org/x/sys/unix/vgetrandom_unsupported.go new file mode 100644 index 0000000000..297e97bce9 --- /dev/null +++ b/vendor/golang.org/x/sys/unix/vgetrandom_unsupported.go @@ -0,0 +1,11 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !linux || !go1.24 + +package unix + +func vgetrandom(p []byte, flags uint32) (ret int, supported bool) { + return -1, false +} diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux.go b/vendor/golang.org/x/sys/unix/zerrors_linux.go index 01a70b2463..de3b462489 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux.go @@ -495,6 +495,7 @@ const ( BPF_F_TEST_REG_INVARIANTS = 0x80 BPF_F_TEST_RND_HI32 = 0x4 BPF_F_TEST_RUN_ON_CPU = 0x1 + BPF_F_TEST_SKB_CHECKSUM_COMPLETE = 0x4 BPF_F_TEST_STATE_FREQ = 0x8 BPF_F_TEST_XDP_LIVE_FRAMES = 0x2 BPF_F_XDP_DEV_BOUND_ONLY = 0x40 @@ -1922,6 +1923,7 @@ const ( MNT_EXPIRE = 0x4 MNT_FORCE = 0x1 MNT_ID_REQ_SIZE_VER0 = 0x18 + MNT_ID_REQ_SIZE_VER1 = 0x20 MODULE_INIT_COMPRESSED_FILE = 0x4 MODULE_INIT_IGNORE_MODVERSIONS = 0x1 MODULE_INIT_IGNORE_VERMAGIC = 0x2 @@ -2187,7 +2189,7 @@ const ( NFT_REG_SIZE = 0x10 NFT_REJECT_ICMPX_MAX = 0x3 NFT_RT_MAX = 0x4 - NFT_SECMARK_CTX_MAXLEN = 0x100 + NFT_SECMARK_CTX_MAXLEN = 0x1000 NFT_SET_MAXNAMELEN = 0x100 NFT_SOCKET_MAX = 0x3 NFT_TABLE_F_MASK = 0x7 @@ -2356,9 +2358,11 @@ const ( PERF_MEM_LVLNUM_IO = 0xa PERF_MEM_LVLNUM_L1 = 0x1 PERF_MEM_LVLNUM_L2 = 0x2 + PERF_MEM_LVLNUM_L2_MHB = 0x5 PERF_MEM_LVLNUM_L3 = 0x3 PERF_MEM_LVLNUM_L4 = 0x4 PERF_MEM_LVLNUM_LFB = 0xc + PERF_MEM_LVLNUM_MSC = 0x6 PERF_MEM_LVLNUM_NA = 0xf PERF_MEM_LVLNUM_PMEM = 0xe PERF_MEM_LVLNUM_RAM = 0xd @@ -2431,6 +2435,7 @@ const ( PRIO_PGRP = 0x1 PRIO_PROCESS = 0x0 PRIO_USER = 0x2 + PROCFS_IOCTL_MAGIC = 'f' PROC_SUPER_MAGIC = 0x9fa0 PROT_EXEC = 0x4 PROT_GROWSDOWN = 0x1000000 @@ -2933,11 +2938,12 @@ const ( RUSAGE_SELF = 0x0 RUSAGE_THREAD = 0x1 RWF_APPEND = 0x10 + RWF_ATOMIC = 0x40 RWF_DSYNC = 0x2 RWF_HIPRI = 0x1 RWF_NOAPPEND = 0x20 RWF_NOWAIT = 0x8 - RWF_SUPPORTED = 0x3f + RWF_SUPPORTED = 0x7f RWF_SYNC = 0x4 RWF_WRITE_LIFE_NOT_SET = 0x0 SCHED_BATCH = 0x3 @@ -3210,6 +3216,7 @@ const ( STATX_ATTR_MOUNT_ROOT = 0x2000 STATX_ATTR_NODUMP = 0x40 STATX_ATTR_VERITY = 0x100000 + STATX_ATTR_WRITE_ATOMIC = 0x400000 STATX_BASIC_STATS = 0x7ff STATX_BLOCKS = 0x400 STATX_BTIME = 0x800 @@ -3226,6 +3233,7 @@ const ( STATX_SUBVOL = 0x8000 STATX_TYPE = 0x1 STATX_UID = 0x8 + STATX_WRITE_ATOMIC = 0x10000 STATX__RESERVED = 0x80000000 SYNC_FILE_RANGE_WAIT_AFTER = 0x4 SYNC_FILE_RANGE_WAIT_BEFORE = 0x1 @@ -3624,6 +3632,7 @@ const ( XDP_UMEM_PGOFF_COMPLETION_RING = 0x180000000 XDP_UMEM_PGOFF_FILL_RING = 0x100000000 XDP_UMEM_REG = 0x4 + XDP_UMEM_TX_METADATA_LEN = 0x4 XDP_UMEM_TX_SW_CSUM = 0x2 XDP_UMEM_UNALIGNED_CHUNK_FLAG = 0x1 XDP_USE_NEED_WAKEUP = 0x8 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_386.go b/vendor/golang.org/x/sys/unix/zerrors_linux_386.go index 684a5168da..8aa6d77c01 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_386.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_386.go @@ -153,9 +153,14 @@ const ( NFDBITS = 0x20 NLDLY = 0x100 NOFLSH = 0x80 + NS_GET_MNTNS_ID = 0x8008b705 NS_GET_NSTYPE = 0xb703 NS_GET_OWNER_UID = 0xb704 NS_GET_PARENT = 0xb702 + NS_GET_PID_FROM_PIDNS = 0x8004b706 + NS_GET_PID_IN_PIDNS = 0x8004b708 + NS_GET_TGID_FROM_PIDNS = 0x8004b707 + NS_GET_TGID_IN_PIDNS = 0x8004b709 NS_GET_USERNS = 0xb701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go index 61d74b592d..da428f4253 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go @@ -153,9 +153,14 @@ const ( NFDBITS = 0x40 NLDLY = 0x100 NOFLSH = 0x80 + NS_GET_MNTNS_ID = 0x8008b705 NS_GET_NSTYPE = 0xb703 NS_GET_OWNER_UID = 0xb704 NS_GET_PARENT = 0xb702 + NS_GET_PID_FROM_PIDNS = 0x8004b706 + NS_GET_PID_IN_PIDNS = 0x8004b708 + NS_GET_TGID_FROM_PIDNS = 0x8004b707 + NS_GET_TGID_IN_PIDNS = 0x8004b709 NS_GET_USERNS = 0xb701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go b/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go index a28c9e3e89..bf45bfec78 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go @@ -150,9 +150,14 @@ const ( NFDBITS = 0x20 NLDLY = 0x100 NOFLSH = 0x80 + NS_GET_MNTNS_ID = 0x8008b705 NS_GET_NSTYPE = 0xb703 NS_GET_OWNER_UID = 0xb704 NS_GET_PARENT = 0xb702 + NS_GET_PID_FROM_PIDNS = 0x8004b706 + NS_GET_PID_IN_PIDNS = 0x8004b708 + NS_GET_TGID_FROM_PIDNS = 0x8004b707 + NS_GET_TGID_IN_PIDNS = 0x8004b709 NS_GET_USERNS = 0xb701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go index ab5d1fe8ea..71c67162b7 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go @@ -154,9 +154,14 @@ const ( NFDBITS = 0x40 NLDLY = 0x100 NOFLSH = 0x80 + NS_GET_MNTNS_ID = 0x8008b705 NS_GET_NSTYPE = 0xb703 NS_GET_OWNER_UID = 0xb704 NS_GET_PARENT = 0xb702 + NS_GET_PID_FROM_PIDNS = 0x8004b706 + NS_GET_PID_IN_PIDNS = 0x8004b708 + NS_GET_TGID_FROM_PIDNS = 0x8004b707 + NS_GET_TGID_IN_PIDNS = 0x8004b709 NS_GET_USERNS = 0xb701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go index c523090e7c..9476628fa0 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go @@ -154,9 +154,14 @@ const ( NFDBITS = 0x40 NLDLY = 0x100 NOFLSH = 0x80 + NS_GET_MNTNS_ID = 0x8008b705 NS_GET_NSTYPE = 0xb703 NS_GET_OWNER_UID = 0xb704 NS_GET_PARENT = 0xb702 + NS_GET_PID_FROM_PIDNS = 0x8004b706 + NS_GET_PID_IN_PIDNS = 0x8004b708 + NS_GET_TGID_FROM_PIDNS = 0x8004b707 + NS_GET_TGID_IN_PIDNS = 0x8004b709 NS_GET_USERNS = 0xb701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go index 01e6ea7804..b9e85f3cf0 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go @@ -150,9 +150,14 @@ const ( NFDBITS = 0x20 NLDLY = 0x100 NOFLSH = 0x80 + NS_GET_MNTNS_ID = 0x4008b705 NS_GET_NSTYPE = 0x2000b703 NS_GET_OWNER_UID = 0x2000b704 NS_GET_PARENT = 0x2000b702 + NS_GET_PID_FROM_PIDNS = 0x4004b706 + NS_GET_PID_IN_PIDNS = 0x4004b708 + NS_GET_TGID_FROM_PIDNS = 0x4004b707 + NS_GET_TGID_IN_PIDNS = 0x4004b709 NS_GET_USERNS = 0x2000b701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go index 7aa610b1e7..a48b68a764 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go @@ -150,9 +150,14 @@ const ( NFDBITS = 0x40 NLDLY = 0x100 NOFLSH = 0x80 + NS_GET_MNTNS_ID = 0x4008b705 NS_GET_NSTYPE = 0x2000b703 NS_GET_OWNER_UID = 0x2000b704 NS_GET_PARENT = 0x2000b702 + NS_GET_PID_FROM_PIDNS = 0x4004b706 + NS_GET_PID_IN_PIDNS = 0x4004b708 + NS_GET_TGID_FROM_PIDNS = 0x4004b707 + NS_GET_TGID_IN_PIDNS = 0x4004b709 NS_GET_USERNS = 0x2000b701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go index 92af771b44..ea00e8522a 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go @@ -150,9 +150,14 @@ const ( NFDBITS = 0x40 NLDLY = 0x100 NOFLSH = 0x80 + NS_GET_MNTNS_ID = 0x4008b705 NS_GET_NSTYPE = 0x2000b703 NS_GET_OWNER_UID = 0x2000b704 NS_GET_PARENT = 0x2000b702 + NS_GET_PID_FROM_PIDNS = 0x4004b706 + NS_GET_PID_IN_PIDNS = 0x4004b708 + NS_GET_TGID_FROM_PIDNS = 0x4004b707 + NS_GET_TGID_IN_PIDNS = 0x4004b709 NS_GET_USERNS = 0x2000b701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go index b27ef5e6f1..91c6468717 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go @@ -150,9 +150,14 @@ const ( NFDBITS = 0x20 NLDLY = 0x100 NOFLSH = 0x80 + NS_GET_MNTNS_ID = 0x4008b705 NS_GET_NSTYPE = 0x2000b703 NS_GET_OWNER_UID = 0x2000b704 NS_GET_PARENT = 0x2000b702 + NS_GET_PID_FROM_PIDNS = 0x4004b706 + NS_GET_PID_IN_PIDNS = 0x4004b708 + NS_GET_TGID_FROM_PIDNS = 0x4004b707 + NS_GET_TGID_IN_PIDNS = 0x4004b709 NS_GET_USERNS = 0x2000b701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go index 237a2cefb3..8cbf38d639 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go @@ -152,9 +152,14 @@ const ( NL3 = 0x300 NLDLY = 0x300 NOFLSH = 0x80000000 + NS_GET_MNTNS_ID = 0x4008b705 NS_GET_NSTYPE = 0x2000b703 NS_GET_OWNER_UID = 0x2000b704 NS_GET_PARENT = 0x2000b702 + NS_GET_PID_FROM_PIDNS = 0x4004b706 + NS_GET_PID_IN_PIDNS = 0x4004b708 + NS_GET_TGID_FROM_PIDNS = 0x4004b707 + NS_GET_TGID_IN_PIDNS = 0x4004b709 NS_GET_USERNS = 0x2000b701 OLCUC = 0x4 ONLCR = 0x2 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go index 4a5c555a36..a2df734191 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go @@ -152,9 +152,14 @@ const ( NL3 = 0x300 NLDLY = 0x300 NOFLSH = 0x80000000 + NS_GET_MNTNS_ID = 0x4008b705 NS_GET_NSTYPE = 0x2000b703 NS_GET_OWNER_UID = 0x2000b704 NS_GET_PARENT = 0x2000b702 + NS_GET_PID_FROM_PIDNS = 0x4004b706 + NS_GET_PID_IN_PIDNS = 0x4004b708 + NS_GET_TGID_FROM_PIDNS = 0x4004b707 + NS_GET_TGID_IN_PIDNS = 0x4004b709 NS_GET_USERNS = 0x2000b701 OLCUC = 0x4 ONLCR = 0x2 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go index a02fb49a5f..2479137923 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go @@ -152,9 +152,14 @@ const ( NL3 = 0x300 NLDLY = 0x300 NOFLSH = 0x80000000 + NS_GET_MNTNS_ID = 0x4008b705 NS_GET_NSTYPE = 0x2000b703 NS_GET_OWNER_UID = 0x2000b704 NS_GET_PARENT = 0x2000b702 + NS_GET_PID_FROM_PIDNS = 0x4004b706 + NS_GET_PID_IN_PIDNS = 0x4004b708 + NS_GET_TGID_FROM_PIDNS = 0x4004b707 + NS_GET_TGID_IN_PIDNS = 0x4004b709 NS_GET_USERNS = 0x2000b701 OLCUC = 0x4 ONLCR = 0x2 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go index e26a7c61b2..d265f146ee 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go @@ -150,9 +150,14 @@ const ( NFDBITS = 0x40 NLDLY = 0x100 NOFLSH = 0x80 + NS_GET_MNTNS_ID = 0x8008b705 NS_GET_NSTYPE = 0xb703 NS_GET_OWNER_UID = 0xb704 NS_GET_PARENT = 0xb702 + NS_GET_PID_FROM_PIDNS = 0x8004b706 + NS_GET_PID_IN_PIDNS = 0x8004b708 + NS_GET_TGID_FROM_PIDNS = 0x8004b707 + NS_GET_TGID_IN_PIDNS = 0x8004b709 NS_GET_USERNS = 0xb701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go b/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go index c48f7c2103..3f2d644396 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go @@ -150,9 +150,14 @@ const ( NFDBITS = 0x40 NLDLY = 0x100 NOFLSH = 0x80 + NS_GET_MNTNS_ID = 0x8008b705 NS_GET_NSTYPE = 0xb703 NS_GET_OWNER_UID = 0xb704 NS_GET_PARENT = 0xb702 + NS_GET_PID_FROM_PIDNS = 0x8004b706 + NS_GET_PID_IN_PIDNS = 0x8004b708 + NS_GET_TGID_FROM_PIDNS = 0x8004b707 + NS_GET_TGID_IN_PIDNS = 0x8004b709 NS_GET_USERNS = 0xb701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go index ad4b9aace7..5d8b727a1c 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go @@ -155,9 +155,14 @@ const ( NFDBITS = 0x40 NLDLY = 0x100 NOFLSH = 0x80 + NS_GET_MNTNS_ID = 0x4008b705 NS_GET_NSTYPE = 0x2000b703 NS_GET_OWNER_UID = 0x2000b704 NS_GET_PARENT = 0x2000b702 + NS_GET_PID_FROM_PIDNS = 0x4004b706 + NS_GET_PID_IN_PIDNS = 0x4004b708 + NS_GET_TGID_FROM_PIDNS = 0x4004b707 + NS_GET_TGID_IN_PIDNS = 0x4004b709 NS_GET_USERNS = 0x2000b701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux.go b/vendor/golang.org/x/sys/unix/zsyscall_linux.go index 1bc1a5adb2..af30da5578 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_linux.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_linux.go @@ -971,23 +971,6 @@ func Getpriority(which int, who int) (prio int, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func Getrandom(buf []byte, flags int) (n int, err error) { - var _p0 unsafe.Pointer - if len(buf) > 0 { - _p0 = unsafe.Pointer(&buf[0]) - } else { - _p0 = unsafe.Pointer(&_zero) - } - r0, _, e1 := Syscall(SYS_GETRANDOM, uintptr(_p0), uintptr(len(buf)), uintptr(flags)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func Getrusage(who int, rusage *Rusage) (err error) { _, _, e1 := RawSyscall(SYS_GETRUSAGE, uintptr(who), uintptr(unsafe.Pointer(rusage)), 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go index d3e38f681a..f485dbf456 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go @@ -341,6 +341,7 @@ const ( SYS_STATX = 332 SYS_IO_PGETEVENTS = 333 SYS_RSEQ = 334 + SYS_URETPROBE = 335 SYS_PIDFD_SEND_SIGNAL = 424 SYS_IO_URING_SETUP = 425 SYS_IO_URING_ENTER = 426 diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go index 6c778c2327..1893e2fe88 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go @@ -85,7 +85,7 @@ const ( SYS_SPLICE = 76 SYS_TEE = 77 SYS_READLINKAT = 78 - SYS_FSTATAT = 79 + SYS_NEWFSTATAT = 79 SYS_FSTAT = 80 SYS_SYNC = 81 SYS_FSYNC = 82 diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go index 37281cf51a..16a4017da0 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go @@ -84,6 +84,8 @@ const ( SYS_SPLICE = 76 SYS_TEE = 77 SYS_READLINKAT = 78 + SYS_NEWFSTATAT = 79 + SYS_FSTAT = 80 SYS_SYNC = 81 SYS_FSYNC = 82 SYS_FDATASYNC = 83 diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go index 9889f6a559..a5459e766f 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go @@ -84,7 +84,7 @@ const ( SYS_SPLICE = 76 SYS_TEE = 77 SYS_READLINKAT = 78 - SYS_FSTATAT = 79 + SYS_NEWFSTATAT = 79 SYS_FSTAT = 80 SYS_SYNC = 81 SYS_FSYNC = 82 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux.go b/vendor/golang.org/x/sys/unix/ztypes_linux.go index 9f2550dc31..3a69e45496 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux.go @@ -87,31 +87,35 @@ type StatxTimestamp struct { } type Statx_t struct { - Mask uint32 - Blksize uint32 - Attributes uint64 - Nlink uint32 - Uid uint32 - Gid uint32 - Mode uint16 - _ [1]uint16 - Ino uint64 - Size uint64 - Blocks uint64 - Attributes_mask uint64 - Atime StatxTimestamp - Btime StatxTimestamp - Ctime StatxTimestamp - Mtime StatxTimestamp - Rdev_major uint32 - Rdev_minor uint32 - Dev_major uint32 - Dev_minor uint32 - Mnt_id uint64 - Dio_mem_align uint32 - Dio_offset_align uint32 - Subvol uint64 - _ [11]uint64 + Mask uint32 + Blksize uint32 + Attributes uint64 + Nlink uint32 + Uid uint32 + Gid uint32 + Mode uint16 + _ [1]uint16 + Ino uint64 + Size uint64 + Blocks uint64 + Attributes_mask uint64 + Atime StatxTimestamp + Btime StatxTimestamp + Ctime StatxTimestamp + Mtime StatxTimestamp + Rdev_major uint32 + Rdev_minor uint32 + Dev_major uint32 + Dev_minor uint32 + Mnt_id uint64 + Dio_mem_align uint32 + Dio_offset_align uint32 + Subvol uint64 + Atomic_write_unit_min uint32 + Atomic_write_unit_max uint32 + Atomic_write_segments_max uint32 + _ [1]uint32 + _ [9]uint64 } type Fsid struct { @@ -516,6 +520,29 @@ type TCPInfo struct { Total_rto_time uint32 } +type TCPVegasInfo struct { + Enabled uint32 + Rttcnt uint32 + Rtt uint32 + Minrtt uint32 +} + +type TCPDCTCPInfo struct { + Enabled uint16 + Ce_state uint16 + Alpha uint32 + Ab_ecn uint32 + Ab_tot uint32 +} + +type TCPBBRInfo struct { + Bw_lo uint32 + Bw_hi uint32 + Min_rtt uint32 + Pacing_gain uint32 + Cwnd_gain uint32 +} + type CanFilter struct { Id uint32 Mask uint32 @@ -557,6 +584,7 @@ const ( SizeofICMPv6Filter = 0x20 SizeofUcred = 0xc SizeofTCPInfo = 0xf8 + SizeofTCPCCInfo = 0x14 SizeofCanFilter = 0x8 SizeofTCPRepairOpt = 0x8 ) @@ -3766,7 +3794,7 @@ const ( ETHTOOL_MSG_PSE_GET = 0x24 ETHTOOL_MSG_PSE_SET = 0x25 ETHTOOL_MSG_RSS_GET = 0x26 - ETHTOOL_MSG_USER_MAX = 0x2b + ETHTOOL_MSG_USER_MAX = 0x2c ETHTOOL_MSG_KERNEL_NONE = 0x0 ETHTOOL_MSG_STRSET_GET_REPLY = 0x1 ETHTOOL_MSG_LINKINFO_GET_REPLY = 0x2 @@ -3806,7 +3834,7 @@ const ( ETHTOOL_MSG_MODULE_NTF = 0x24 ETHTOOL_MSG_PSE_GET_REPLY = 0x25 ETHTOOL_MSG_RSS_GET_REPLY = 0x26 - ETHTOOL_MSG_KERNEL_MAX = 0x2b + ETHTOOL_MSG_KERNEL_MAX = 0x2c ETHTOOL_FLAG_COMPACT_BITSETS = 0x1 ETHTOOL_FLAG_OMIT_REPLY = 0x2 ETHTOOL_FLAG_STATS = 0x4 @@ -3951,7 +3979,7 @@ const ( ETHTOOL_A_COALESCE_RATE_SAMPLE_INTERVAL = 0x17 ETHTOOL_A_COALESCE_USE_CQE_MODE_TX = 0x18 ETHTOOL_A_COALESCE_USE_CQE_MODE_RX = 0x19 - ETHTOOL_A_COALESCE_MAX = 0x1c + ETHTOOL_A_COALESCE_MAX = 0x1e ETHTOOL_A_PAUSE_UNSPEC = 0x0 ETHTOOL_A_PAUSE_HEADER = 0x1 ETHTOOL_A_PAUSE_AUTONEG = 0x2 @@ -4609,7 +4637,7 @@ const ( NL80211_ATTR_MAC_HINT = 0xc8 NL80211_ATTR_MAC_MASK = 0xd7 NL80211_ATTR_MAX_AP_ASSOC_STA = 0xca - NL80211_ATTR_MAX = 0x14a + NL80211_ATTR_MAX = 0x14c NL80211_ATTR_MAX_CRIT_PROT_DURATION = 0xb4 NL80211_ATTR_MAX_CSA_COUNTERS = 0xce NL80211_ATTR_MAX_MATCH_SETS = 0x85 @@ -5213,7 +5241,7 @@ const ( NL80211_FREQUENCY_ATTR_GO_CONCURRENT = 0xf NL80211_FREQUENCY_ATTR_INDOOR_ONLY = 0xe NL80211_FREQUENCY_ATTR_IR_CONCURRENT = 0xf - NL80211_FREQUENCY_ATTR_MAX = 0x20 + NL80211_FREQUENCY_ATTR_MAX = 0x21 NL80211_FREQUENCY_ATTR_MAX_TX_POWER = 0x6 NL80211_FREQUENCY_ATTR_NO_10MHZ = 0x11 NL80211_FREQUENCY_ATTR_NO_160MHZ = 0xc diff --git a/vendor/golang.org/x/sys/windows/dll_windows.go b/vendor/golang.org/x/sys/windows/dll_windows.go index 115341fba6..4e613cf633 100644 --- a/vendor/golang.org/x/sys/windows/dll_windows.go +++ b/vendor/golang.org/x/sys/windows/dll_windows.go @@ -65,7 +65,7 @@ func LoadDLL(name string) (dll *DLL, err error) { return d, nil } -// MustLoadDLL is like LoadDLL but panics if load operation failes. +// MustLoadDLL is like LoadDLL but panics if load operation fails. func MustLoadDLL(name string) *DLL { d, e := LoadDLL(name) if e != nil { diff --git a/vendor/golang.org/x/time/rate/rate.go b/vendor/golang.org/x/time/rate/rate.go index 8f6c7f493f..93a798ab63 100644 --- a/vendor/golang.org/x/time/rate/rate.go +++ b/vendor/golang.org/x/time/rate/rate.go @@ -99,8 +99,9 @@ func (lim *Limiter) Tokens() float64 { // bursts of at most b tokens. func NewLimiter(r Limit, b int) *Limiter { return &Limiter{ - limit: r, - burst: b, + limit: r, + burst: b, + tokens: float64(b), } } @@ -344,18 +345,6 @@ func (lim *Limiter) reserveN(t time.Time, n int, maxFutureReserve time.Duration) tokens: n, timeToAct: t, } - } else if lim.limit == 0 { - var ok bool - if lim.burst >= n { - ok = true - lim.burst -= n - } - return Reservation{ - ok: ok, - lim: lim, - tokens: lim.burst, - timeToAct: t, - } } t, tokens := lim.advance(t) diff --git a/vendor/golang.org/x/tools/LICENSE b/vendor/golang.org/x/tools/LICENSE new file mode 100644 index 0000000000..2a7cf70da6 --- /dev/null +++ b/vendor/golang.org/x/tools/LICENSE @@ -0,0 +1,27 @@ +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/tools/PATENTS b/vendor/golang.org/x/tools/PATENTS new file mode 100644 index 0000000000..733099041f --- /dev/null +++ b/vendor/golang.org/x/tools/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/tools/txtar/archive.go b/vendor/golang.org/x/tools/txtar/archive.go new file mode 100644 index 0000000000..fd95f1e64a --- /dev/null +++ b/vendor/golang.org/x/tools/txtar/archive.go @@ -0,0 +1,140 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package txtar implements a trivial text-based file archive format. +// +// The goals for the format are: +// +// - be trivial enough to create and edit by hand. +// - be able to store trees of text files describing go command test cases. +// - diff nicely in git history and code reviews. +// +// Non-goals include being a completely general archive format, +// storing binary data, storing file modes, storing special files like +// symbolic links, and so on. +// +// # Txtar format +// +// A txtar archive is zero or more comment lines and then a sequence of file entries. +// Each file entry begins with a file marker line of the form "-- FILENAME --" +// and is followed by zero or more file content lines making up the file data. +// The comment or file content ends at the next file marker line. +// The file marker line must begin with the three-byte sequence "-- " +// and end with the three-byte sequence " --", but the enclosed +// file name can be surrounding by additional white space, +// all of which is stripped. +// +// If the txtar file is missing a trailing newline on the final line, +// parsers should consider a final newline to be present anyway. +// +// There are no possible syntax errors in a txtar archive. +package txtar + +import ( + "bytes" + "fmt" + "os" + "strings" +) + +// An Archive is a collection of files. +type Archive struct { + Comment []byte + Files []File +} + +// A File is a single file in an archive. +type File struct { + Name string // name of file ("foo/bar.txt") + Data []byte // text content of file +} + +// Format returns the serialized form of an Archive. +// It is assumed that the Archive data structure is well-formed: +// a.Comment and all a.File[i].Data contain no file marker lines, +// and all a.File[i].Name is non-empty. +func Format(a *Archive) []byte { + var buf bytes.Buffer + buf.Write(fixNL(a.Comment)) + for _, f := range a.Files { + fmt.Fprintf(&buf, "-- %s --\n", f.Name) + buf.Write(fixNL(f.Data)) + } + return buf.Bytes() +} + +// ParseFile parses the named file as an archive. +func ParseFile(file string) (*Archive, error) { + data, err := os.ReadFile(file) + if err != nil { + return nil, err + } + return Parse(data), nil +} + +// Parse parses the serialized form of an Archive. +// The returned Archive holds slices of data. +func Parse(data []byte) *Archive { + a := new(Archive) + var name string + a.Comment, name, data = findFileMarker(data) + for name != "" { + f := File{name, nil} + f.Data, name, data = findFileMarker(data) + a.Files = append(a.Files, f) + } + return a +} + +var ( + newlineMarker = []byte("\n-- ") + marker = []byte("-- ") + markerEnd = []byte(" --") +) + +// findFileMarker finds the next file marker in data, +// extracts the file name, and returns the data before the marker, +// the file name, and the data after the marker. +// If there is no next marker, findFileMarker returns before = fixNL(data), name = "", after = nil. +func findFileMarker(data []byte) (before []byte, name string, after []byte) { + var i int + for { + if name, after = isMarker(data[i:]); name != "" { + return data[:i], name, after + } + j := bytes.Index(data[i:], newlineMarker) + if j < 0 { + return fixNL(data), "", nil + } + i += j + 1 // positioned at start of new possible marker + } +} + +// isMarker checks whether data begins with a file marker line. +// If so, it returns the name from the line and the data after the line. +// Otherwise it returns name == "" with an unspecified after. +func isMarker(data []byte) (name string, after []byte) { + if !bytes.HasPrefix(data, marker) { + return "", nil + } + if i := bytes.IndexByte(data, '\n'); i >= 0 { + data, after = data[:i], data[i+1:] + } + if !(bytes.HasSuffix(data, markerEnd) && len(data) >= len(marker)+len(markerEnd)) { + return "", nil + } + return strings.TrimSpace(string(data[len(marker) : len(data)-len(markerEnd)])), after +} + +// If data is empty or ends in \n, fixNL returns data. +// Otherwise fixNL returns a new slice consisting of data with a final \n added. +func fixNL(data []byte) []byte { + if len(data) == 0 || data[len(data)-1] == '\n' { + return data + } + d := make([]byte, len(data)+1) + copy(d, data) + d[len(data)] = '\n' + return d +} diff --git a/vendor/golang.org/x/tools/txtar/fs.go b/vendor/golang.org/x/tools/txtar/fs.go new file mode 100644 index 0000000000..fc8df12c18 --- /dev/null +++ b/vendor/golang.org/x/tools/txtar/fs.go @@ -0,0 +1,257 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package txtar + +import ( + "errors" + "fmt" + "io" + "io/fs" + "path" + "slices" + "time" +) + +// FS returns the file system form of an Archive. +// It returns an error if any of the file names in the archive +// are not valid file system names. +// The archive must not be modified while the FS is in use. +// +// If the file system detects that it has been modified, calls to the +// file system return an ErrModified error. +func FS(a *Archive) (fs.FS, error) { + // Create a filesystem with a root directory. + root := &node{fileinfo: fileinfo{path: ".", mode: readOnlyDir}} + fsys := &filesystem{a, map[string]*node{root.path: root}} + + if err := initFiles(fsys); err != nil { + return nil, fmt.Errorf("cannot create fs.FS from txtar.Archive: %s", err) + } + return fsys, nil +} + +const ( + readOnly fs.FileMode = 0o444 // read only mode + readOnlyDir = readOnly | fs.ModeDir +) + +// ErrModified indicates that file system returned by FS +// noticed that the underlying archive has been modified +// since the call to FS. Detection of modification is best effort, +// to help diagnose misuse of the API, and is not guaranteed. +var ErrModified error = errors.New("txtar.Archive has been modified during txtar.FS") + +// A filesystem is a simple in-memory file system for txtar archives, +// represented as a map from valid path names to information about the +// files or directories they represent. +// +// File system operations are read only. Modifications to the underlying +// *Archive may race. To help prevent this, the filesystem tries +// to detect modification during Open and return ErrModified if it +// is able to detect a modification. +type filesystem struct { + ar *Archive + nodes map[string]*node +} + +// node is a file or directory in the tree of a filesystem. +type node struct { + fileinfo // fs.FileInfo and fs.DirEntry implementation + idx int // index into ar.Files (for files) + entries []fs.DirEntry // subdirectories and files (for directories) +} + +var _ fs.FS = (*filesystem)(nil) +var _ fs.DirEntry = (*node)(nil) + +// initFiles initializes fsys from fsys.ar.Files. Returns an error if there are any +// invalid file names or collisions between file or directories. +func initFiles(fsys *filesystem) error { + for idx, file := range fsys.ar.Files { + name := file.Name + if !fs.ValidPath(name) { + return fmt.Errorf("file %q is an invalid path", name) + } + + n := &node{idx: idx, fileinfo: fileinfo{path: name, size: len(file.Data), mode: readOnly}} + if err := insert(fsys, n); err != nil { + return err + } + } + return nil +} + +// insert adds node n as an entry to its parent directory within the filesystem. +func insert(fsys *filesystem, n *node) error { + if m := fsys.nodes[n.path]; m != nil { + return fmt.Errorf("duplicate path %q", n.path) + } + fsys.nodes[n.path] = n + + // fsys.nodes contains "." to prevent infinite loops. + parent, err := directory(fsys, path.Dir(n.path)) + if err != nil { + return err + } + parent.entries = append(parent.entries, n) + return nil +} + +// directory returns the directory node with the path dir and lazily-creates it +// if it does not exist. +func directory(fsys *filesystem, dir string) (*node, error) { + if m := fsys.nodes[dir]; m != nil && m.IsDir() { + return m, nil // pre-existing directory + } + + n := &node{fileinfo: fileinfo{path: dir, mode: readOnlyDir}} + if err := insert(fsys, n); err != nil { + return nil, err + } + return n, nil +} + +// dataOf returns the data associated with the file t. +// May return ErrModified if fsys.ar has been modified. +func dataOf(fsys *filesystem, n *node) ([]byte, error) { + if n.idx >= len(fsys.ar.Files) { + return nil, ErrModified + } + + f := fsys.ar.Files[n.idx] + if f.Name != n.path || len(f.Data) != n.size { + return nil, ErrModified + } + return f.Data, nil +} + +func (fsys *filesystem) Open(name string) (fs.File, error) { + if !fs.ValidPath(name) { + return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrInvalid} + } + + n := fsys.nodes[name] + switch { + case n == nil: + return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist} + case n.IsDir(): + return &openDir{fileinfo: n.fileinfo, entries: n.entries}, nil + default: + data, err := dataOf(fsys, n) + if err != nil { + return nil, err + } + return &openFile{fileinfo: n.fileinfo, data: data}, nil + } +} + +func (fsys *filesystem) ReadFile(name string) ([]byte, error) { + file, err := fsys.Open(name) + if err != nil { + return nil, err + } + if file, ok := file.(*openFile); ok { + return slices.Clone(file.data), nil + } + return nil, &fs.PathError{Op: "read", Path: name, Err: fs.ErrInvalid} +} + +// A fileinfo implements fs.FileInfo and fs.DirEntry for a given archive file. +type fileinfo struct { + path string // unique path to the file or directory within a filesystem + size int + mode fs.FileMode +} + +var _ fs.FileInfo = (*fileinfo)(nil) +var _ fs.DirEntry = (*fileinfo)(nil) + +func (i *fileinfo) Name() string { return path.Base(i.path) } +func (i *fileinfo) Size() int64 { return int64(i.size) } +func (i *fileinfo) Mode() fs.FileMode { return i.mode } +func (i *fileinfo) Type() fs.FileMode { return i.mode.Type() } +func (i *fileinfo) ModTime() time.Time { return time.Time{} } +func (i *fileinfo) IsDir() bool { return i.mode&fs.ModeDir != 0 } +func (i *fileinfo) Sys() any { return nil } +func (i *fileinfo) Info() (fs.FileInfo, error) { return i, nil } + +// An openFile is a regular (non-directory) fs.File open for reading. +type openFile struct { + fileinfo + data []byte + offset int64 +} + +var _ fs.File = (*openFile)(nil) + +func (f *openFile) Stat() (fs.FileInfo, error) { return &f.fileinfo, nil } +func (f *openFile) Close() error { return nil } +func (f *openFile) Read(b []byte) (int, error) { + if f.offset >= int64(len(f.data)) { + return 0, io.EOF + } + if f.offset < 0 { + return 0, &fs.PathError{Op: "read", Path: f.path, Err: fs.ErrInvalid} + } + n := copy(b, f.data[f.offset:]) + f.offset += int64(n) + return n, nil +} + +func (f *openFile) Seek(offset int64, whence int) (int64, error) { + switch whence { + case 0: + // offset += 0 + case 1: + offset += f.offset + case 2: + offset += int64(len(f.data)) + } + if offset < 0 || offset > int64(len(f.data)) { + return 0, &fs.PathError{Op: "seek", Path: f.path, Err: fs.ErrInvalid} + } + f.offset = offset + return offset, nil +} + +func (f *openFile) ReadAt(b []byte, offset int64) (int, error) { + if offset < 0 || offset > int64(len(f.data)) { + return 0, &fs.PathError{Op: "read", Path: f.path, Err: fs.ErrInvalid} + } + n := copy(b, f.data[offset:]) + if n < len(b) { + return n, io.EOF + } + return n, nil +} + +// A openDir is a directory fs.File (so also an fs.ReadDirFile) open for reading. +type openDir struct { + fileinfo + entries []fs.DirEntry + offset int +} + +var _ fs.ReadDirFile = (*openDir)(nil) + +func (d *openDir) Stat() (fs.FileInfo, error) { return &d.fileinfo, nil } +func (d *openDir) Close() error { return nil } +func (d *openDir) Read(b []byte) (int, error) { + return 0, &fs.PathError{Op: "read", Path: d.path, Err: fs.ErrInvalid} +} + +func (d *openDir) ReadDir(count int) ([]fs.DirEntry, error) { + n := len(d.entries) - d.offset + if n == 0 && count > 0 { + return nil, io.EOF + } + if count > 0 && n > count { + n = count + } + list := make([]fs.DirEntry, n) + copy(list, d.entries[d.offset:d.offset+n]) + d.offset += n + return list, nil +} diff --git a/vendor/google.golang.org/grpc/internal/transport/transport.go b/vendor/google.golang.org/grpc/internal/transport/transport.go index fdd6fa86cc..924ba4f365 100644 --- a/vendor/google.golang.org/grpc/internal/transport/transport.go +++ b/vendor/google.golang.org/grpc/internal/transport/transport.go @@ -616,7 +616,7 @@ func (t *transportReader) ReadHeader(header []byte) (int, error) { t.er = err return 0, err } - t.windowHandler(len(header)) + t.windowHandler(n) return n, nil } diff --git a/vendor/google.golang.org/grpc/version.go b/vendor/google.golang.org/grpc/version.go index 187fbf1195..a96b6a6bff 100644 --- a/vendor/google.golang.org/grpc/version.go +++ b/vendor/google.golang.org/grpc/version.go @@ -19,4 +19,4 @@ package grpc // Version is the current grpc version. -const Version = "1.67.0" +const Version = "1.67.1" diff --git a/vendor/google.golang.org/protobuf/encoding/protojson/decode.go b/vendor/google.golang.org/protobuf/encoding/protojson/decode.go index bb2966e3b4..8f9e592f87 100644 --- a/vendor/google.golang.org/protobuf/encoding/protojson/decode.go +++ b/vendor/google.golang.org/protobuf/encoding/protojson/decode.go @@ -351,7 +351,7 @@ func (d decoder) unmarshalScalar(fd protoreflect.FieldDescriptor) (protoreflect. panic(fmt.Sprintf("unmarshalScalar: invalid scalar kind %v", kind)) } - return protoreflect.Value{}, d.newError(tok.Pos(), "invalid value for %v type: %v", kind, tok.RawString()) + return protoreflect.Value{}, d.newError(tok.Pos(), "invalid value for %v field %v: %v", kind, fd.JSONName(), tok.RawString()) } func unmarshalInt(tok json.Token, bitSize int) (protoreflect.Value, bool) { diff --git a/vendor/google.golang.org/protobuf/encoding/protojson/encode.go b/vendor/google.golang.org/protobuf/encoding/protojson/encode.go index 29846df222..0e72d85378 100644 --- a/vendor/google.golang.org/protobuf/encoding/protojson/encode.go +++ b/vendor/google.golang.org/protobuf/encoding/protojson/encode.go @@ -216,9 +216,7 @@ func (m unpopulatedFieldRanger) Range(f func(protoreflect.FieldDescriptor, proto } v := m.Get(fd) - isProto2Scalar := fd.Syntax() == protoreflect.Proto2 && fd.Default().IsValid() - isSingularMessage := fd.Cardinality() != protoreflect.Repeated && fd.Message() != nil - if isProto2Scalar || isSingularMessage { + if fd.HasPresence() { if m.skipNull { continue } diff --git a/vendor/google.golang.org/protobuf/internal/descopts/options.go b/vendor/google.golang.org/protobuf/internal/descopts/options.go index 8401be8c84..024ffebd3d 100644 --- a/vendor/google.golang.org/protobuf/internal/descopts/options.go +++ b/vendor/google.golang.org/protobuf/internal/descopts/options.go @@ -9,7 +9,7 @@ // dependency on the descriptor proto package). package descopts -import pref "google.golang.org/protobuf/reflect/protoreflect" +import "google.golang.org/protobuf/reflect/protoreflect" // These variables are set by the init function in descriptor.pb.go via logic // in internal/filetype. In other words, so long as the descriptor proto package @@ -17,13 +17,13 @@ import pref "google.golang.org/protobuf/reflect/protoreflect" // // Each variable is populated with a nil pointer to the options struct. var ( - File pref.ProtoMessage - Enum pref.ProtoMessage - EnumValue pref.ProtoMessage - Message pref.ProtoMessage - Field pref.ProtoMessage - Oneof pref.ProtoMessage - ExtensionRange pref.ProtoMessage - Service pref.ProtoMessage - Method pref.ProtoMessage + File protoreflect.ProtoMessage + Enum protoreflect.ProtoMessage + EnumValue protoreflect.ProtoMessage + Message protoreflect.ProtoMessage + Field protoreflect.ProtoMessage + Oneof protoreflect.ProtoMessage + ExtensionRange protoreflect.ProtoMessage + Service protoreflect.ProtoMessage + Method protoreflect.ProtoMessage ) diff --git a/vendor/google.golang.org/protobuf/internal/editionssupport/editions.go b/vendor/google.golang.org/protobuf/internal/editionssupport/editions.go index 029a6a12d7..08dad7692c 100644 --- a/vendor/google.golang.org/protobuf/internal/editionssupport/editions.go +++ b/vendor/google.golang.org/protobuf/internal/editionssupport/editions.go @@ -5,7 +5,7 @@ // Package editionssupport defines constants for editions that are supported. package editionssupport -import descriptorpb "google.golang.org/protobuf/types/descriptorpb" +import "google.golang.org/protobuf/types/descriptorpb" const ( Minimum = descriptorpb.Edition_EDITION_PROTO2 diff --git a/vendor/google.golang.org/protobuf/internal/filedesc/desc.go b/vendor/google.golang.org/protobuf/internal/filedesc/desc.go index df53ff40b2..fa790e0ff1 100644 --- a/vendor/google.golang.org/protobuf/internal/filedesc/desc.go +++ b/vendor/google.golang.org/protobuf/internal/filedesc/desc.go @@ -258,6 +258,7 @@ type ( StringName stringName IsProto3Optional bool // promoted from google.protobuf.FieldDescriptorProto IsWeak bool // promoted from google.protobuf.FieldOptions + IsLazy bool // promoted from google.protobuf.FieldOptions Default defaultValue ContainingOneof protoreflect.OneofDescriptor // must be consistent with Message.Oneofs.Fields Enum protoreflect.EnumDescriptor @@ -351,6 +352,7 @@ func (fd *Field) IsPacked() bool { } func (fd *Field) IsExtension() bool { return false } func (fd *Field) IsWeak() bool { return fd.L1.IsWeak } +func (fd *Field) IsLazy() bool { return fd.L1.IsLazy } func (fd *Field) IsList() bool { return fd.Cardinality() == protoreflect.Repeated && !fd.IsMap() } func (fd *Field) IsMap() bool { return fd.Message() != nil && fd.Message().IsMapEntry() } func (fd *Field) MapKey() protoreflect.FieldDescriptor { @@ -425,6 +427,7 @@ type ( Extendee protoreflect.MessageDescriptor Cardinality protoreflect.Cardinality Kind protoreflect.Kind + IsLazy bool EditionFeatures EditionFeatures } ExtensionL2 struct { @@ -465,6 +468,7 @@ func (xd *Extension) IsPacked() bool { } func (xd *Extension) IsExtension() bool { return true } func (xd *Extension) IsWeak() bool { return false } +func (xd *Extension) IsLazy() bool { return xd.L1.IsLazy } func (xd *Extension) IsList() bool { return xd.Cardinality() == protoreflect.Repeated } func (xd *Extension) IsMap() bool { return false } func (xd *Extension) MapKey() protoreflect.FieldDescriptor { return nil } diff --git a/vendor/google.golang.org/protobuf/internal/filedesc/desc_init.go b/vendor/google.golang.org/protobuf/internal/filedesc/desc_init.go index 8a57d60b08..d2f549497e 100644 --- a/vendor/google.golang.org/protobuf/internal/filedesc/desc_init.go +++ b/vendor/google.golang.org/protobuf/internal/filedesc/desc_init.go @@ -495,6 +495,8 @@ func (xd *Extension) unmarshalOptions(b []byte) { switch num { case genid.FieldOptions_Packed_field_number: xd.L1.EditionFeatures.IsPacked = protowire.DecodeBool(v) + case genid.FieldOptions_Lazy_field_number: + xd.L1.IsLazy = protowire.DecodeBool(v) } case protowire.BytesType: v, m := protowire.ConsumeBytes(b) diff --git a/vendor/google.golang.org/protobuf/internal/filedesc/desc_lazy.go b/vendor/google.golang.org/protobuf/internal/filedesc/desc_lazy.go index e56c91a8db..67a51b327c 100644 --- a/vendor/google.golang.org/protobuf/internal/filedesc/desc_lazy.go +++ b/vendor/google.golang.org/protobuf/internal/filedesc/desc_lazy.go @@ -504,6 +504,8 @@ func (fd *Field) unmarshalOptions(b []byte) { fd.L1.EditionFeatures.IsPacked = protowire.DecodeBool(v) case genid.FieldOptions_Weak_field_number: fd.L1.IsWeak = protowire.DecodeBool(v) + case genid.FieldOptions_Lazy_field_number: + fd.L1.IsLazy = protowire.DecodeBool(v) case FieldOptions_EnforceUTF8: fd.L1.EditionFeatures.IsUTF8Validated = protowire.DecodeBool(v) } diff --git a/vendor/google.golang.org/protobuf/internal/filedesc/editions.go b/vendor/google.golang.org/protobuf/internal/filedesc/editions.go index 11f5f356b6..fd4d0c83d2 100644 --- a/vendor/google.golang.org/protobuf/internal/filedesc/editions.go +++ b/vendor/google.golang.org/protobuf/internal/filedesc/editions.go @@ -68,7 +68,7 @@ func unmarshalFeatureSet(b []byte, parent EditionFeatures) EditionFeatures { v, m := protowire.ConsumeBytes(b) b = b[m:] switch num { - case genid.GoFeatures_LegacyUnmarshalJsonEnum_field_number: + case genid.FeatureSet_Go_ext_number: parent = unmarshalGoFeature(v, parent) } } diff --git a/vendor/google.golang.org/protobuf/internal/genid/doc.go b/vendor/google.golang.org/protobuf/internal/genid/doc.go index 45ccd01211..d9b9d916a2 100644 --- a/vendor/google.golang.org/protobuf/internal/genid/doc.go +++ b/vendor/google.golang.org/protobuf/internal/genid/doc.go @@ -6,6 +6,6 @@ // and the well-known types. package genid -import protoreflect "google.golang.org/protobuf/reflect/protoreflect" +import "google.golang.org/protobuf/reflect/protoreflect" const GoogleProtobuf_package protoreflect.FullName = "google.protobuf" diff --git a/vendor/google.golang.org/protobuf/internal/genid/go_features_gen.go b/vendor/google.golang.org/protobuf/internal/genid/go_features_gen.go index 9a652a2b42..7f67cbb6e9 100644 --- a/vendor/google.golang.org/protobuf/internal/genid/go_features_gen.go +++ b/vendor/google.golang.org/protobuf/internal/genid/go_features_gen.go @@ -12,20 +12,25 @@ import ( const File_google_protobuf_go_features_proto = "google/protobuf/go_features.proto" -// Names for google.protobuf.GoFeatures. +// Names for pb.GoFeatures. const ( GoFeatures_message_name protoreflect.Name = "GoFeatures" - GoFeatures_message_fullname protoreflect.FullName = "google.protobuf.GoFeatures" + GoFeatures_message_fullname protoreflect.FullName = "pb.GoFeatures" ) -// Field names for google.protobuf.GoFeatures. +// Field names for pb.GoFeatures. const ( GoFeatures_LegacyUnmarshalJsonEnum_field_name protoreflect.Name = "legacy_unmarshal_json_enum" - GoFeatures_LegacyUnmarshalJsonEnum_field_fullname protoreflect.FullName = "google.protobuf.GoFeatures.legacy_unmarshal_json_enum" + GoFeatures_LegacyUnmarshalJsonEnum_field_fullname protoreflect.FullName = "pb.GoFeatures.legacy_unmarshal_json_enum" ) -// Field numbers for google.protobuf.GoFeatures. +// Field numbers for pb.GoFeatures. const ( GoFeatures_LegacyUnmarshalJsonEnum_field_number protoreflect.FieldNumber = 1 ) + +// Extension numbers +const ( + FeatureSet_Go_ext_number protoreflect.FieldNumber = 1002 +) diff --git a/vendor/google.golang.org/protobuf/internal/genid/map_entry.go b/vendor/google.golang.org/protobuf/internal/genid/map_entry.go index 8f9ea02ff2..bef5a25fbb 100644 --- a/vendor/google.golang.org/protobuf/internal/genid/map_entry.go +++ b/vendor/google.golang.org/protobuf/internal/genid/map_entry.go @@ -4,7 +4,7 @@ package genid -import protoreflect "google.golang.org/protobuf/reflect/protoreflect" +import "google.golang.org/protobuf/reflect/protoreflect" // Generic field names and numbers for synthetic map entry messages. const ( diff --git a/vendor/google.golang.org/protobuf/internal/genid/wrappers.go b/vendor/google.golang.org/protobuf/internal/genid/wrappers.go index 429384b85b..9404270de0 100644 --- a/vendor/google.golang.org/protobuf/internal/genid/wrappers.go +++ b/vendor/google.golang.org/protobuf/internal/genid/wrappers.go @@ -4,7 +4,7 @@ package genid -import protoreflect "google.golang.org/protobuf/reflect/protoreflect" +import "google.golang.org/protobuf/reflect/protoreflect" // Generic field name and number for messages in wrappers.proto. const ( diff --git a/vendor/google.golang.org/protobuf/internal/impl/codec_extension.go b/vendor/google.golang.org/protobuf/internal/impl/codec_extension.go index 4bb0a7a20c..0d5b546e0e 100644 --- a/vendor/google.golang.org/protobuf/internal/impl/codec_extension.go +++ b/vendor/google.golang.org/protobuf/internal/impl/codec_extension.go @@ -67,7 +67,6 @@ type lazyExtensionValue struct { xi *extensionFieldInfo value protoreflect.Value b []byte - fn func() protoreflect.Value } type ExtensionField struct { @@ -158,10 +157,9 @@ func (f *ExtensionField) lazyInit() { } f.lazy.value = val } else { - f.lazy.value = f.lazy.fn() + panic("No support for lazy fns for ExtensionField") } f.lazy.xi = nil - f.lazy.fn = nil f.lazy.b = nil atomic.StoreUint32(&f.lazy.atomicOnce, 1) } @@ -174,13 +172,6 @@ func (f *ExtensionField) Set(t protoreflect.ExtensionType, v protoreflect.Value) f.lazy = nil } -// SetLazy sets the type and a value that is to be lazily evaluated upon first use. -// This must not be called concurrently. -func (f *ExtensionField) SetLazy(t protoreflect.ExtensionType, fn func() protoreflect.Value) { - f.typ = t - f.lazy = &lazyExtensionValue{fn: fn} -} - // Value returns the value of the extension field. // This may be called concurrently. func (f *ExtensionField) Value() protoreflect.Value { diff --git a/vendor/google.golang.org/protobuf/internal/impl/codec_field.go b/vendor/google.golang.org/protobuf/internal/impl/codec_field.go index 78ee47e44b..7c1f66c8c1 100644 --- a/vendor/google.golang.org/protobuf/internal/impl/codec_field.go +++ b/vendor/google.golang.org/protobuf/internal/impl/codec_field.go @@ -65,6 +65,9 @@ func (mi *MessageInfo) initOneofFieldCoders(od protoreflect.OneofDescriptor, si if err != nil { return out, err } + if cf.funcs.isInit == nil { + out.initialized = true + } vi.Set(vw) return out, nil } diff --git a/vendor/google.golang.org/protobuf/internal/impl/codec_message.go b/vendor/google.golang.org/protobuf/internal/impl/codec_message.go index 6b2fdbb739..78be9df342 100644 --- a/vendor/google.golang.org/protobuf/internal/impl/codec_message.go +++ b/vendor/google.golang.org/protobuf/internal/impl/codec_message.go @@ -189,6 +189,9 @@ func (mi *MessageInfo) makeCoderMethods(t reflect.Type, si structInfo) { if mi.methods.Merge == nil { mi.methods.Merge = mi.merge } + if mi.methods.Equal == nil { + mi.methods.Equal = equal + } } // getUnknownBytes returns a *[]byte for the unknown fields. diff --git a/vendor/google.golang.org/protobuf/internal/impl/codec_reflect.go b/vendor/google.golang.org/protobuf/internal/impl/codec_reflect.go deleted file mode 100644 index 145c577bd6..0000000000 --- a/vendor/google.golang.org/protobuf/internal/impl/codec_reflect.go +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build purego || appengine -// +build purego appengine - -package impl - -import ( - "reflect" - - "google.golang.org/protobuf/encoding/protowire" -) - -func sizeEnum(p pointer, f *coderFieldInfo, _ marshalOptions) (size int) { - v := p.v.Elem().Int() - return f.tagsize + protowire.SizeVarint(uint64(v)) -} - -func appendEnum(b []byte, p pointer, f *coderFieldInfo, opts marshalOptions) ([]byte, error) { - v := p.v.Elem().Int() - b = protowire.AppendVarint(b, f.wiretag) - b = protowire.AppendVarint(b, uint64(v)) - return b, nil -} - -func consumeEnum(b []byte, p pointer, wtyp protowire.Type, f *coderFieldInfo, _ unmarshalOptions) (out unmarshalOutput, err error) { - if wtyp != protowire.VarintType { - return out, errUnknown - } - v, n := protowire.ConsumeVarint(b) - if n < 0 { - return out, errDecode - } - p.v.Elem().SetInt(int64(v)) - out.n = n - return out, nil -} - -func mergeEnum(dst, src pointer, _ *coderFieldInfo, _ mergeOptions) { - dst.v.Elem().Set(src.v.Elem()) -} - -var coderEnum = pointerCoderFuncs{ - size: sizeEnum, - marshal: appendEnum, - unmarshal: consumeEnum, - merge: mergeEnum, -} - -func sizeEnumNoZero(p pointer, f *coderFieldInfo, opts marshalOptions) (size int) { - if p.v.Elem().Int() == 0 { - return 0 - } - return sizeEnum(p, f, opts) -} - -func appendEnumNoZero(b []byte, p pointer, f *coderFieldInfo, opts marshalOptions) ([]byte, error) { - if p.v.Elem().Int() == 0 { - return b, nil - } - return appendEnum(b, p, f, opts) -} - -func mergeEnumNoZero(dst, src pointer, _ *coderFieldInfo, _ mergeOptions) { - if src.v.Elem().Int() != 0 { - dst.v.Elem().Set(src.v.Elem()) - } -} - -var coderEnumNoZero = pointerCoderFuncs{ - size: sizeEnumNoZero, - marshal: appendEnumNoZero, - unmarshal: consumeEnum, - merge: mergeEnumNoZero, -} - -func sizeEnumPtr(p pointer, f *coderFieldInfo, opts marshalOptions) (size int) { - return sizeEnum(pointer{p.v.Elem()}, f, opts) -} - -func appendEnumPtr(b []byte, p pointer, f *coderFieldInfo, opts marshalOptions) ([]byte, error) { - return appendEnum(b, pointer{p.v.Elem()}, f, opts) -} - -func consumeEnumPtr(b []byte, p pointer, wtyp protowire.Type, f *coderFieldInfo, opts unmarshalOptions) (out unmarshalOutput, err error) { - if wtyp != protowire.VarintType { - return out, errUnknown - } - if p.v.Elem().IsNil() { - p.v.Elem().Set(reflect.New(p.v.Elem().Type().Elem())) - } - return consumeEnum(b, pointer{p.v.Elem()}, wtyp, f, opts) -} - -func mergeEnumPtr(dst, src pointer, _ *coderFieldInfo, _ mergeOptions) { - if !src.v.Elem().IsNil() { - v := reflect.New(dst.v.Type().Elem().Elem()) - v.Elem().Set(src.v.Elem().Elem()) - dst.v.Elem().Set(v) - } -} - -var coderEnumPtr = pointerCoderFuncs{ - size: sizeEnumPtr, - marshal: appendEnumPtr, - unmarshal: consumeEnumPtr, - merge: mergeEnumPtr, -} - -func sizeEnumSlice(p pointer, f *coderFieldInfo, opts marshalOptions) (size int) { - s := p.v.Elem() - for i, llen := 0, s.Len(); i < llen; i++ { - size += protowire.SizeVarint(uint64(s.Index(i).Int())) + f.tagsize - } - return size -} - -func appendEnumSlice(b []byte, p pointer, f *coderFieldInfo, opts marshalOptions) ([]byte, error) { - s := p.v.Elem() - for i, llen := 0, s.Len(); i < llen; i++ { - b = protowire.AppendVarint(b, f.wiretag) - b = protowire.AppendVarint(b, uint64(s.Index(i).Int())) - } - return b, nil -} - -func consumeEnumSlice(b []byte, p pointer, wtyp protowire.Type, f *coderFieldInfo, opts unmarshalOptions) (out unmarshalOutput, err error) { - s := p.v.Elem() - if wtyp == protowire.BytesType { - b, n := protowire.ConsumeBytes(b) - if n < 0 { - return out, errDecode - } - for len(b) > 0 { - v, n := protowire.ConsumeVarint(b) - if n < 0 { - return out, errDecode - } - rv := reflect.New(s.Type().Elem()).Elem() - rv.SetInt(int64(v)) - s.Set(reflect.Append(s, rv)) - b = b[n:] - } - out.n = n - return out, nil - } - if wtyp != protowire.VarintType { - return out, errUnknown - } - v, n := protowire.ConsumeVarint(b) - if n < 0 { - return out, errDecode - } - rv := reflect.New(s.Type().Elem()).Elem() - rv.SetInt(int64(v)) - s.Set(reflect.Append(s, rv)) - out.n = n - return out, nil -} - -func mergeEnumSlice(dst, src pointer, _ *coderFieldInfo, _ mergeOptions) { - dst.v.Elem().Set(reflect.AppendSlice(dst.v.Elem(), src.v.Elem())) -} - -var coderEnumSlice = pointerCoderFuncs{ - size: sizeEnumSlice, - marshal: appendEnumSlice, - unmarshal: consumeEnumSlice, - merge: mergeEnumSlice, -} - -func sizeEnumPackedSlice(p pointer, f *coderFieldInfo, opts marshalOptions) (size int) { - s := p.v.Elem() - llen := s.Len() - if llen == 0 { - return 0 - } - n := 0 - for i := 0; i < llen; i++ { - n += protowire.SizeVarint(uint64(s.Index(i).Int())) - } - return f.tagsize + protowire.SizeBytes(n) -} - -func appendEnumPackedSlice(b []byte, p pointer, f *coderFieldInfo, opts marshalOptions) ([]byte, error) { - s := p.v.Elem() - llen := s.Len() - if llen == 0 { - return b, nil - } - b = protowire.AppendVarint(b, f.wiretag) - n := 0 - for i := 0; i < llen; i++ { - n += protowire.SizeVarint(uint64(s.Index(i).Int())) - } - b = protowire.AppendVarint(b, uint64(n)) - for i := 0; i < llen; i++ { - b = protowire.AppendVarint(b, uint64(s.Index(i).Int())) - } - return b, nil -} - -var coderEnumPackedSlice = pointerCoderFuncs{ - size: sizeEnumPackedSlice, - marshal: appendEnumPackedSlice, - unmarshal: consumeEnumSlice, - merge: mergeEnumSlice, -} diff --git a/vendor/google.golang.org/protobuf/internal/impl/codec_unsafe.go b/vendor/google.golang.org/protobuf/internal/impl/codec_unsafe.go index 757642e23c..077712c2c5 100644 --- a/vendor/google.golang.org/protobuf/internal/impl/codec_unsafe.go +++ b/vendor/google.golang.org/protobuf/internal/impl/codec_unsafe.go @@ -2,9 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !purego && !appengine -// +build !purego,!appengine - package impl // When using unsafe pointers, we can just treat enum values as int32s. diff --git a/vendor/google.golang.org/protobuf/internal/impl/convert.go b/vendor/google.golang.org/protobuf/internal/impl/convert.go index e06ece55a2..f72ddd882f 100644 --- a/vendor/google.golang.org/protobuf/internal/impl/convert.go +++ b/vendor/google.golang.org/protobuf/internal/impl/convert.go @@ -322,7 +322,7 @@ func (c *stringConverter) PBValueOf(v reflect.Value) protoreflect.Value { return protoreflect.ValueOfString(v.Convert(stringType).String()) } func (c *stringConverter) GoValueOf(v protoreflect.Value) reflect.Value { - // pref.Value.String never panics, so we go through an interface + // protoreflect.Value.String never panics, so we go through an interface // conversion here to check the type. s := v.Interface().(string) if c.goType.Kind() == reflect.Slice && s == "" { diff --git a/vendor/google.golang.org/protobuf/internal/impl/encode.go b/vendor/google.golang.org/protobuf/internal/impl/encode.go index febd212247..6254f5de41 100644 --- a/vendor/google.golang.org/protobuf/internal/impl/encode.go +++ b/vendor/google.golang.org/protobuf/internal/impl/encode.go @@ -10,7 +10,7 @@ import ( "sync/atomic" "google.golang.org/protobuf/internal/flags" - proto "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/proto" piface "google.golang.org/protobuf/runtime/protoiface" ) diff --git a/vendor/google.golang.org/protobuf/internal/impl/equal.go b/vendor/google.golang.org/protobuf/internal/impl/equal.go new file mode 100644 index 0000000000..9f6c32a7d8 --- /dev/null +++ b/vendor/google.golang.org/protobuf/internal/impl/equal.go @@ -0,0 +1,224 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package impl + +import ( + "bytes" + + "google.golang.org/protobuf/encoding/protowire" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/runtime/protoiface" +) + +func equal(in protoiface.EqualInput) protoiface.EqualOutput { + return protoiface.EqualOutput{Equal: equalMessage(in.MessageA, in.MessageB)} +} + +// equalMessage is a fast-path variant of protoreflect.equalMessage. +// It takes advantage of the internal messageState type to avoid +// unnecessary allocations, type assertions. +func equalMessage(mx, my protoreflect.Message) bool { + if mx == nil || my == nil { + return mx == my + } + if mx.Descriptor() != my.Descriptor() { + return false + } + + msx, ok := mx.(*messageState) + if !ok { + return protoreflect.ValueOfMessage(mx).Equal(protoreflect.ValueOfMessage(my)) + } + msy, ok := my.(*messageState) + if !ok { + return protoreflect.ValueOfMessage(mx).Equal(protoreflect.ValueOfMessage(my)) + } + + mi := msx.messageInfo() + miy := msy.messageInfo() + if mi != miy { + return protoreflect.ValueOfMessage(mx).Equal(protoreflect.ValueOfMessage(my)) + } + mi.init() + // Compares regular fields + // Modified Message.Range code that compares two messages of the same type + // while going over the fields. + for _, ri := range mi.rangeInfos { + var fd protoreflect.FieldDescriptor + var vx, vy protoreflect.Value + + switch ri := ri.(type) { + case *fieldInfo: + hx := ri.has(msx.pointer()) + hy := ri.has(msy.pointer()) + if hx != hy { + return false + } + if !hx { + continue + } + fd = ri.fieldDesc + vx = ri.get(msx.pointer()) + vy = ri.get(msy.pointer()) + case *oneofInfo: + fnx := ri.which(msx.pointer()) + fny := ri.which(msy.pointer()) + if fnx != fny { + return false + } + if fnx <= 0 { + continue + } + fi := mi.fields[fnx] + fd = fi.fieldDesc + vx = fi.get(msx.pointer()) + vy = fi.get(msy.pointer()) + } + + if !equalValue(fd, vx, vy) { + return false + } + } + + // Compare extensions. + // This is more complicated because mx or my could have empty/nil extension maps, + // however some populated extension map values are equal to nil extension maps. + emx := mi.extensionMap(msx.pointer()) + emy := mi.extensionMap(msy.pointer()) + if emx != nil { + for k, x := range *emx { + xd := x.Type().TypeDescriptor() + xv := x.Value() + var y ExtensionField + ok := false + if emy != nil { + y, ok = (*emy)[k] + } + // We need to treat empty lists as equal to nil values + if emy == nil || !ok { + if xd.IsList() && xv.List().Len() == 0 { + continue + } + return false + } + + if !equalValue(xd, xv, y.Value()) { + return false + } + } + } + if emy != nil { + // emy may have extensions emx does not have, need to check them as well + for k, y := range *emy { + if emx != nil { + // emx has the field, so we already checked it + if _, ok := (*emx)[k]; ok { + continue + } + } + // Empty lists are equal to nil + if y.Type().TypeDescriptor().IsList() && y.Value().List().Len() == 0 { + continue + } + + // Cant be equal if the extension is populated + return false + } + } + + return equalUnknown(mx.GetUnknown(), my.GetUnknown()) +} + +func equalValue(fd protoreflect.FieldDescriptor, vx, vy protoreflect.Value) bool { + // slow path + if fd.Kind() != protoreflect.MessageKind { + return vx.Equal(vy) + } + + // fast path special cases + if fd.IsMap() { + if fd.MapValue().Kind() == protoreflect.MessageKind { + return equalMessageMap(vx.Map(), vy.Map()) + } + return vx.Equal(vy) + } + + if fd.IsList() { + return equalMessageList(vx.List(), vy.List()) + } + + return equalMessage(vx.Message(), vy.Message()) +} + +// Mostly copied from protoreflect.equalMap. +// This variant only works for messages as map types. +// All other map types should be handled via Value.Equal. +func equalMessageMap(mx, my protoreflect.Map) bool { + if mx.Len() != my.Len() { + return false + } + equal := true + mx.Range(func(k protoreflect.MapKey, vx protoreflect.Value) bool { + if !my.Has(k) { + equal = false + return false + } + vy := my.Get(k) + equal = equalMessage(vx.Message(), vy.Message()) + return equal + }) + return equal +} + +// Mostly copied from protoreflect.equalList. +// The only change is the usage of equalImpl instead of protoreflect.equalValue. +func equalMessageList(lx, ly protoreflect.List) bool { + if lx.Len() != ly.Len() { + return false + } + for i := 0; i < lx.Len(); i++ { + // We only operate on messages here since equalImpl will not call us in any other case. + if !equalMessage(lx.Get(i).Message(), ly.Get(i).Message()) { + return false + } + } + return true +} + +// equalUnknown compares unknown fields by direct comparison on the raw bytes +// of each individual field number. +// Copied from protoreflect.equalUnknown. +func equalUnknown(x, y protoreflect.RawFields) bool { + if len(x) != len(y) { + return false + } + if bytes.Equal([]byte(x), []byte(y)) { + return true + } + + mx := make(map[protoreflect.FieldNumber]protoreflect.RawFields) + my := make(map[protoreflect.FieldNumber]protoreflect.RawFields) + for len(x) > 0 { + fnum, _, n := protowire.ConsumeField(x) + mx[fnum] = append(mx[fnum], x[:n]...) + x = x[n:] + } + for len(y) > 0 { + fnum, _, n := protowire.ConsumeField(y) + my[fnum] = append(my[fnum], y[:n]...) + y = y[n:] + } + if len(mx) != len(my) { + return false + } + + for k, v1 := range mx { + if v2, ok := my[k]; !ok || !bytes.Equal([]byte(v1), []byte(v2)) { + return false + } + } + + return true +} diff --git a/vendor/google.golang.org/protobuf/internal/impl/legacy_extension.go b/vendor/google.golang.org/protobuf/internal/impl/legacy_extension.go index 6e8677ee63..b6849d6692 100644 --- a/vendor/google.golang.org/protobuf/internal/impl/legacy_extension.go +++ b/vendor/google.golang.org/protobuf/internal/impl/legacy_extension.go @@ -160,6 +160,7 @@ func (x placeholderExtension) HasPresence() bool func (x placeholderExtension) HasOptionalKeyword() bool { return false } func (x placeholderExtension) IsExtension() bool { return true } func (x placeholderExtension) IsWeak() bool { return false } +func (x placeholderExtension) IsLazy() bool { return false } func (x placeholderExtension) IsPacked() bool { return false } func (x placeholderExtension) IsList() bool { return false } func (x placeholderExtension) IsMap() bool { return false } diff --git a/vendor/google.golang.org/protobuf/internal/impl/message.go b/vendor/google.golang.org/protobuf/internal/impl/message.go index 019399d454..741b5ed29c 100644 --- a/vendor/google.golang.org/protobuf/internal/impl/message.go +++ b/vendor/google.golang.org/protobuf/internal/impl/message.go @@ -30,8 +30,8 @@ type MessageInfo struct { // Desc is the underlying message descriptor type and must be populated. Desc protoreflect.MessageDescriptor - // Exporter must be provided in a purego environment in order to provide - // access to unexported fields. + // Deprecated: Exporter will be removed the next time we bump + // protoimpl.GenVersion. See https://github.com/golang/protobuf/issues/1640 Exporter exporter // OneofWrappers is list of pointers to oneof wrapper struct types. diff --git a/vendor/google.golang.org/protobuf/internal/impl/pointer_reflect.go b/vendor/google.golang.org/protobuf/internal/impl/pointer_reflect.go deleted file mode 100644 index da685e8a29..0000000000 --- a/vendor/google.golang.org/protobuf/internal/impl/pointer_reflect.go +++ /dev/null @@ -1,215 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build purego || appengine -// +build purego appengine - -package impl - -import ( - "fmt" - "reflect" - "sync" -) - -const UnsafeEnabled = false - -// Pointer is an opaque pointer type. -type Pointer any - -// offset represents the offset to a struct field, accessible from a pointer. -// The offset is the field index into a struct. -type offset struct { - index int - export exporter -} - -// offsetOf returns a field offset for the struct field. -func offsetOf(f reflect.StructField, x exporter) offset { - if len(f.Index) != 1 { - panic("embedded structs are not supported") - } - if f.PkgPath == "" { - return offset{index: f.Index[0]} // field is already exported - } - if x == nil { - panic("exporter must be provided for unexported field") - } - return offset{index: f.Index[0], export: x} -} - -// IsValid reports whether the offset is valid. -func (f offset) IsValid() bool { return f.index >= 0 } - -// invalidOffset is an invalid field offset. -var invalidOffset = offset{index: -1} - -// zeroOffset is a noop when calling pointer.Apply. -var zeroOffset = offset{index: 0} - -// pointer is an abstract representation of a pointer to a struct or field. -type pointer struct{ v reflect.Value } - -// pointerOf returns p as a pointer. -func pointerOf(p Pointer) pointer { - return pointerOfIface(p) -} - -// pointerOfValue returns v as a pointer. -func pointerOfValue(v reflect.Value) pointer { - return pointer{v: v} -} - -// pointerOfIface returns the pointer portion of an interface. -func pointerOfIface(v any) pointer { - return pointer{v: reflect.ValueOf(v)} -} - -// IsNil reports whether the pointer is nil. -func (p pointer) IsNil() bool { - return p.v.IsNil() -} - -// Apply adds an offset to the pointer to derive a new pointer -// to a specified field. The current pointer must be pointing at a struct. -func (p pointer) Apply(f offset) pointer { - if f.export != nil { - if v := reflect.ValueOf(f.export(p.v.Interface(), f.index)); v.IsValid() { - return pointer{v: v} - } - } - return pointer{v: p.v.Elem().Field(f.index).Addr()} -} - -// AsValueOf treats p as a pointer to an object of type t and returns the value. -// It is equivalent to reflect.ValueOf(p.AsIfaceOf(t)) -func (p pointer) AsValueOf(t reflect.Type) reflect.Value { - if got := p.v.Type().Elem(); got != t { - panic(fmt.Sprintf("invalid type: got %v, want %v", got, t)) - } - return p.v -} - -// AsIfaceOf treats p as a pointer to an object of type t and returns the value. -// It is equivalent to p.AsValueOf(t).Interface() -func (p pointer) AsIfaceOf(t reflect.Type) any { - return p.AsValueOf(t).Interface() -} - -func (p pointer) Bool() *bool { return p.v.Interface().(*bool) } -func (p pointer) BoolPtr() **bool { return p.v.Interface().(**bool) } -func (p pointer) BoolSlice() *[]bool { return p.v.Interface().(*[]bool) } -func (p pointer) Int32() *int32 { return p.v.Interface().(*int32) } -func (p pointer) Int32Ptr() **int32 { return p.v.Interface().(**int32) } -func (p pointer) Int32Slice() *[]int32 { return p.v.Interface().(*[]int32) } -func (p pointer) Int64() *int64 { return p.v.Interface().(*int64) } -func (p pointer) Int64Ptr() **int64 { return p.v.Interface().(**int64) } -func (p pointer) Int64Slice() *[]int64 { return p.v.Interface().(*[]int64) } -func (p pointer) Uint32() *uint32 { return p.v.Interface().(*uint32) } -func (p pointer) Uint32Ptr() **uint32 { return p.v.Interface().(**uint32) } -func (p pointer) Uint32Slice() *[]uint32 { return p.v.Interface().(*[]uint32) } -func (p pointer) Uint64() *uint64 { return p.v.Interface().(*uint64) } -func (p pointer) Uint64Ptr() **uint64 { return p.v.Interface().(**uint64) } -func (p pointer) Uint64Slice() *[]uint64 { return p.v.Interface().(*[]uint64) } -func (p pointer) Float32() *float32 { return p.v.Interface().(*float32) } -func (p pointer) Float32Ptr() **float32 { return p.v.Interface().(**float32) } -func (p pointer) Float32Slice() *[]float32 { return p.v.Interface().(*[]float32) } -func (p pointer) Float64() *float64 { return p.v.Interface().(*float64) } -func (p pointer) Float64Ptr() **float64 { return p.v.Interface().(**float64) } -func (p pointer) Float64Slice() *[]float64 { return p.v.Interface().(*[]float64) } -func (p pointer) String() *string { return p.v.Interface().(*string) } -func (p pointer) StringPtr() **string { return p.v.Interface().(**string) } -func (p pointer) StringSlice() *[]string { return p.v.Interface().(*[]string) } -func (p pointer) Bytes() *[]byte { return p.v.Interface().(*[]byte) } -func (p pointer) BytesPtr() **[]byte { return p.v.Interface().(**[]byte) } -func (p pointer) BytesSlice() *[][]byte { return p.v.Interface().(*[][]byte) } -func (p pointer) WeakFields() *weakFields { return (*weakFields)(p.v.Interface().(*WeakFields)) } -func (p pointer) Extensions() *map[int32]ExtensionField { - return p.v.Interface().(*map[int32]ExtensionField) -} - -func (p pointer) Elem() pointer { - return pointer{v: p.v.Elem()} -} - -// PointerSlice copies []*T from p as a new []pointer. -// This behavior differs from the implementation in pointer_unsafe.go. -func (p pointer) PointerSlice() []pointer { - // TODO: reconsider this - if p.v.IsNil() { - return nil - } - n := p.v.Elem().Len() - s := make([]pointer, n) - for i := 0; i < n; i++ { - s[i] = pointer{v: p.v.Elem().Index(i)} - } - return s -} - -// AppendPointerSlice appends v to p, which must be a []*T. -func (p pointer) AppendPointerSlice(v pointer) { - sp := p.v.Elem() - sp.Set(reflect.Append(sp, v.v)) -} - -// SetPointer sets *p to v. -func (p pointer) SetPointer(v pointer) { - p.v.Elem().Set(v.v) -} - -func growSlice(p pointer, addCap int) { - // TODO: Once we only support Go 1.20 and newer, use reflect.Grow. - in := p.v.Elem() - out := reflect.MakeSlice(in.Type(), in.Len(), in.Len()+addCap) - reflect.Copy(out, in) - p.v.Elem().Set(out) -} - -func (p pointer) growBoolSlice(addCap int) { - growSlice(p, addCap) -} - -func (p pointer) growInt32Slice(addCap int) { - growSlice(p, addCap) -} - -func (p pointer) growUint32Slice(addCap int) { - growSlice(p, addCap) -} - -func (p pointer) growInt64Slice(addCap int) { - growSlice(p, addCap) -} - -func (p pointer) growUint64Slice(addCap int) { - growSlice(p, addCap) -} - -func (p pointer) growFloat64Slice(addCap int) { - growSlice(p, addCap) -} - -func (p pointer) growFloat32Slice(addCap int) { - growSlice(p, addCap) -} - -func (Export) MessageStateOf(p Pointer) *messageState { panic("not supported") } -func (ms *messageState) pointer() pointer { panic("not supported") } -func (ms *messageState) messageInfo() *MessageInfo { panic("not supported") } -func (ms *messageState) LoadMessageInfo() *MessageInfo { panic("not supported") } -func (ms *messageState) StoreMessageInfo(mi *MessageInfo) { panic("not supported") } - -type atomicNilMessage struct { - once sync.Once - m messageReflectWrapper -} - -func (m *atomicNilMessage) Init(mi *MessageInfo) *messageReflectWrapper { - m.once.Do(func() { - m.m.p = pointerOfIface(reflect.Zero(mi.GoReflectType).Interface()) - m.m.mi = mi - }) - return &m.m -} diff --git a/vendor/google.golang.org/protobuf/internal/impl/pointer_unsafe.go b/vendor/google.golang.org/protobuf/internal/impl/pointer_unsafe.go index 5f20ca5d8a..79e186667b 100644 --- a/vendor/google.golang.org/protobuf/internal/impl/pointer_unsafe.go +++ b/vendor/google.golang.org/protobuf/internal/impl/pointer_unsafe.go @@ -2,9 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !purego && !appengine -// +build !purego,!appengine - package impl import ( diff --git a/vendor/google.golang.org/protobuf/internal/strs/strings_pure.go b/vendor/google.golang.org/protobuf/internal/strs/strings_pure.go deleted file mode 100644 index a1f6f33386..0000000000 --- a/vendor/google.golang.org/protobuf/internal/strs/strings_pure.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build purego || appengine -// +build purego appengine - -package strs - -import pref "google.golang.org/protobuf/reflect/protoreflect" - -func UnsafeString(b []byte) string { - return string(b) -} - -func UnsafeBytes(s string) []byte { - return []byte(s) -} - -type Builder struct{} - -func (*Builder) AppendFullName(prefix pref.FullName, name pref.Name) pref.FullName { - return prefix.Append(name) -} - -func (*Builder) MakeString(b []byte) string { - return string(b) -} diff --git a/vendor/google.golang.org/protobuf/internal/strs/strings_unsafe_go120.go b/vendor/google.golang.org/protobuf/internal/strs/strings_unsafe_go120.go index a008acd090..832a7988f1 100644 --- a/vendor/google.golang.org/protobuf/internal/strs/strings_unsafe_go120.go +++ b/vendor/google.golang.org/protobuf/internal/strs/strings_unsafe_go120.go @@ -2,8 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !purego && !appengine && !go1.21 -// +build !purego,!appengine,!go1.21 +//go:build !go1.21 package strs diff --git a/vendor/google.golang.org/protobuf/internal/strs/strings_unsafe_go121.go b/vendor/google.golang.org/protobuf/internal/strs/strings_unsafe_go121.go index 60166f2ba3..1ffddf6877 100644 --- a/vendor/google.golang.org/protobuf/internal/strs/strings_unsafe_go121.go +++ b/vendor/google.golang.org/protobuf/internal/strs/strings_unsafe_go121.go @@ -2,8 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !purego && !appengine && go1.21 -// +build !purego,!appengine,go1.21 +//go:build go1.21 package strs diff --git a/vendor/google.golang.org/protobuf/internal/version/version.go b/vendor/google.golang.org/protobuf/internal/version/version.go index dbbf1f6862..fb8e15e8da 100644 --- a/vendor/google.golang.org/protobuf/internal/version/version.go +++ b/vendor/google.golang.org/protobuf/internal/version/version.go @@ -51,8 +51,8 @@ import ( // 10. Send out the CL for review and submit it. const ( Major = 1 - Minor = 34 - Patch = 2 + Minor = 35 + Patch = 1 PreRelease = "" ) diff --git a/vendor/google.golang.org/protobuf/proto/equal.go b/vendor/google.golang.org/protobuf/proto/equal.go index 1a0be1b03c..c36d4a9cd7 100644 --- a/vendor/google.golang.org/protobuf/proto/equal.go +++ b/vendor/google.golang.org/protobuf/proto/equal.go @@ -8,6 +8,7 @@ import ( "reflect" "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/runtime/protoiface" ) // Equal reports whether two messages are equal, @@ -51,6 +52,14 @@ func Equal(x, y Message) bool { if mx.IsValid() != my.IsValid() { return false } + + // Only one of the messages needs to implement the fast-path for it to work. + pmx := protoMethods(mx) + pmy := protoMethods(my) + if pmx != nil && pmy != nil && pmx.Equal != nil && pmy.Equal != nil { + return pmx.Equal(protoiface.EqualInput{MessageA: mx, MessageB: my}).Equal + } + vx := protoreflect.ValueOfMessage(mx) vy := protoreflect.ValueOfMessage(my) return vx.Equal(vy) diff --git a/vendor/google.golang.org/protobuf/proto/extension.go b/vendor/google.golang.org/protobuf/proto/extension.go index d248f29284..78445d116f 100644 --- a/vendor/google.golang.org/protobuf/proto/extension.go +++ b/vendor/google.golang.org/protobuf/proto/extension.go @@ -39,6 +39,48 @@ func ClearExtension(m Message, xt protoreflect.ExtensionType) { // If the field is unpopulated, it returns the default value for // scalars and an immutable, empty value for lists or messages. // It panics if xt does not extend m. +// +// The type of the value is dependent on the field type of the extension. +// For extensions generated by protoc-gen-go, the Go type is as follows: +// +// ╔═══════════════════╤═════════════════════════╗ +// ║ Go type │ Protobuf kind ║ +// ╠═══════════════════╪═════════════════════════╣ +// ║ bool │ bool ║ +// ║ int32 │ int32, sint32, sfixed32 ║ +// ║ int64 │ int64, sint64, sfixed64 ║ +// ║ uint32 │ uint32, fixed32 ║ +// ║ uint64 │ uint64, fixed64 ║ +// ║ float32 │ float ║ +// ║ float64 │ double ║ +// ║ string │ string ║ +// ║ []byte │ bytes ║ +// ║ protoreflect.Enum │ enum ║ +// ║ proto.Message │ message, group ║ +// ╚═══════════════════╧═════════════════════════╝ +// +// The protoreflect.Enum and proto.Message types are the concrete Go type +// associated with the named enum or message. Repeated fields are represented +// using a Go slice of the base element type. +// +// If a generated extension descriptor variable is directly passed to +// GetExtension, then the call should be followed immediately by a +// type assertion to the expected output value. For example: +// +// mm := proto.GetExtension(m, foopb.E_MyExtension).(*foopb.MyMessage) +// +// This pattern enables static analysis tools to verify that the asserted type +// matches the Go type associated with the extension field and +// also enables a possible future migration to a type-safe extension API. +// +// Since singular messages are the most common extension type, the pattern of +// calling HasExtension followed by GetExtension may be simplified to: +// +// if mm := proto.GetExtension(m, foopb.E_MyExtension).(*foopb.MyMessage); mm != nil { +// ... // make use of mm +// } +// +// The mm variable is non-nil if and only if HasExtension reports true. func GetExtension(m Message, xt protoreflect.ExtensionType) any { // Treat nil message interface as an empty message; return the default. if m == nil { @@ -51,6 +93,35 @@ func GetExtension(m Message, xt protoreflect.ExtensionType) any { // SetExtension stores the value of an extension field. // It panics if m is invalid, xt does not extend m, or if type of v // is invalid for the specified extension field. +// +// The type of the value is dependent on the field type of the extension. +// For extensions generated by protoc-gen-go, the Go type is as follows: +// +// ╔═══════════════════╤═════════════════════════╗ +// ║ Go type │ Protobuf kind ║ +// ╠═══════════════════╪═════════════════════════╣ +// ║ bool │ bool ║ +// ║ int32 │ int32, sint32, sfixed32 ║ +// ║ int64 │ int64, sint64, sfixed64 ║ +// ║ uint32 │ uint32, fixed32 ║ +// ║ uint64 │ uint64, fixed64 ║ +// ║ float32 │ float ║ +// ║ float64 │ double ║ +// ║ string │ string ║ +// ║ []byte │ bytes ║ +// ║ protoreflect.Enum │ enum ║ +// ║ proto.Message │ message, group ║ +// ╚═══════════════════╧═════════════════════════╝ +// +// The protoreflect.Enum and proto.Message types are the concrete Go type +// associated with the named enum or message. Repeated fields are represented +// using a Go slice of the base element type. +// +// If a generated extension descriptor variable is directly passed to +// SetExtension (e.g., foopb.E_MyExtension), then the value should be a +// concrete type that matches the expected Go type for the extension descriptor +// so that static analysis tools can verify type correctness. +// This also enables a possible future migration to a type-safe extension API. func SetExtension(m Message, xt protoreflect.ExtensionType, v any) { xd := xt.TypeDescriptor() pv := xt.ValueOf(v) diff --git a/vendor/google.golang.org/protobuf/reflect/protodesc/desc_init.go b/vendor/google.golang.org/protobuf/reflect/protodesc/desc_init.go index 8561755427..ebcb4a8ab1 100644 --- a/vendor/google.golang.org/protobuf/reflect/protodesc/desc_init.go +++ b/vendor/google.golang.org/protobuf/reflect/protodesc/desc_init.go @@ -150,6 +150,7 @@ func (r descsByName) initFieldsFromDescriptorProto(fds []*descriptorpb.FieldDesc opts = proto.Clone(opts).(*descriptorpb.FieldOptions) f.L1.Options = func() protoreflect.ProtoMessage { return opts } f.L1.IsWeak = opts.GetWeak() + f.L1.IsLazy = opts.GetLazy() if opts.Packed != nil { f.L1.EditionFeatures.IsPacked = opts.GetPacked() } @@ -214,6 +215,9 @@ func (r descsByName) initExtensionDeclarations(xds []*descriptorpb.FieldDescript if xd.JsonName != nil { x.L2.StringName.InitJSON(xd.GetJsonName()) } + if x.L1.Kind == protoreflect.MessageKind && x.L1.EditionFeatures.IsDelimitedEncoded { + x.L1.Kind = protoreflect.GroupKind + } } return xs, nil } diff --git a/vendor/google.golang.org/protobuf/reflect/protodesc/editions.go b/vendor/google.golang.org/protobuf/reflect/protodesc/editions.go index 804830eda3..002e0047ae 100644 --- a/vendor/google.golang.org/protobuf/reflect/protodesc/editions.go +++ b/vendor/google.golang.org/protobuf/reflect/protodesc/editions.go @@ -14,7 +14,7 @@ import ( "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/types/descriptorpb" - gofeaturespb "google.golang.org/protobuf/types/gofeaturespb" + "google.golang.org/protobuf/types/gofeaturespb" ) var defaults = &descriptorpb.FeatureSetDefaults{} diff --git a/vendor/google.golang.org/protobuf/reflect/protoreflect/methods.go b/vendor/google.golang.org/protobuf/reflect/protoreflect/methods.go index d5d5af6ebe..742cb518c4 100644 --- a/vendor/google.golang.org/protobuf/reflect/protoreflect/methods.go +++ b/vendor/google.golang.org/protobuf/reflect/protoreflect/methods.go @@ -23,6 +23,7 @@ type ( Unmarshal func(unmarshalInput) (unmarshalOutput, error) Merge func(mergeInput) mergeOutput CheckInitialized func(checkInitializedInput) (checkInitializedOutput, error) + Equal func(equalInput) equalOutput } supportFlags = uint64 sizeInput = struct { @@ -75,4 +76,13 @@ type ( checkInitializedOutput = struct { pragma.NoUnkeyedLiterals } + equalInput = struct { + pragma.NoUnkeyedLiterals + MessageA Message + MessageB Message + } + equalOutput = struct { + pragma.NoUnkeyedLiterals + Equal bool + } ) diff --git a/vendor/google.golang.org/protobuf/reflect/protoreflect/value_pure.go b/vendor/google.golang.org/protobuf/reflect/protoreflect/value_pure.go deleted file mode 100644 index 75f83a2af0..0000000000 --- a/vendor/google.golang.org/protobuf/reflect/protoreflect/value_pure.go +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build purego || appengine -// +build purego appengine - -package protoreflect - -import "google.golang.org/protobuf/internal/pragma" - -type valueType int - -const ( - nilType valueType = iota - boolType - int32Type - int64Type - uint32Type - uint64Type - float32Type - float64Type - stringType - bytesType - enumType - ifaceType -) - -// value is a union where only one type can be represented at a time. -// This uses a distinct field for each type. This is type safe in Go, but -// occupies more memory than necessary (72B). -type value struct { - pragma.DoNotCompare // 0B - - typ valueType // 8B - num uint64 // 8B - str string // 16B - bin []byte // 24B - iface any // 16B -} - -func valueOfString(v string) Value { - return Value{typ: stringType, str: v} -} -func valueOfBytes(v []byte) Value { - return Value{typ: bytesType, bin: v} -} -func valueOfIface(v any) Value { - return Value{typ: ifaceType, iface: v} -} - -func (v Value) getString() string { - return v.str -} -func (v Value) getBytes() []byte { - return v.bin -} -func (v Value) getIface() any { - return v.iface -} diff --git a/vendor/google.golang.org/protobuf/reflect/protoreflect/value_unsafe_go120.go b/vendor/google.golang.org/protobuf/reflect/protoreflect/value_unsafe_go120.go index 7f3583ead8..0015fcb35d 100644 --- a/vendor/google.golang.org/protobuf/reflect/protoreflect/value_unsafe_go120.go +++ b/vendor/google.golang.org/protobuf/reflect/protoreflect/value_unsafe_go120.go @@ -2,8 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !purego && !appengine && !go1.21 -// +build !purego,!appengine,!go1.21 +//go:build !go1.21 package protoreflect diff --git a/vendor/google.golang.org/protobuf/reflect/protoreflect/value_unsafe_go121.go b/vendor/google.golang.org/protobuf/reflect/protoreflect/value_unsafe_go121.go index f7d386990a..479527b58d 100644 --- a/vendor/google.golang.org/protobuf/reflect/protoreflect/value_unsafe_go121.go +++ b/vendor/google.golang.org/protobuf/reflect/protoreflect/value_unsafe_go121.go @@ -2,8 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !purego && !appengine && go1.21 -// +build !purego,!appengine,go1.21 +//go:build go1.21 package protoreflect diff --git a/vendor/google.golang.org/protobuf/runtime/protoiface/methods.go b/vendor/google.golang.org/protobuf/runtime/protoiface/methods.go index 44cf467d88..246156561c 100644 --- a/vendor/google.golang.org/protobuf/runtime/protoiface/methods.go +++ b/vendor/google.golang.org/protobuf/runtime/protoiface/methods.go @@ -39,6 +39,9 @@ type Methods = struct { // CheckInitialized returns an error if any required fields in the message are not set. CheckInitialized func(CheckInitializedInput) (CheckInitializedOutput, error) + + // Equal compares two messages and returns EqualOutput.Equal == true if they are equal. + Equal func(EqualInput) EqualOutput } // SupportFlags indicate support for optional features. @@ -166,3 +169,18 @@ type CheckInitializedInput = struct { type CheckInitializedOutput = struct { pragma.NoUnkeyedLiterals } + +// EqualInput is input to the Equal method. +type EqualInput = struct { + pragma.NoUnkeyedLiterals + + MessageA protoreflect.Message + MessageB protoreflect.Message +} + +// EqualOutput is output from the Equal method. +type EqualOutput = struct { + pragma.NoUnkeyedLiterals + + Equal bool +} diff --git a/vendor/google.golang.org/protobuf/types/descriptorpb/descriptor.pb.go b/vendor/google.golang.org/protobuf/types/descriptorpb/descriptor.pb.go index 9403eb0750..6dea75cd5b 100644 --- a/vendor/google.golang.org/protobuf/types/descriptorpb/descriptor.pb.go +++ b/vendor/google.golang.org/protobuf/types/descriptorpb/descriptor.pb.go @@ -1217,11 +1217,9 @@ type FileDescriptorSet struct { func (x *FileDescriptorSet) Reset() { *x = FileDescriptorSet{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_descriptor_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_descriptor_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *FileDescriptorSet) String() string { @@ -1232,7 +1230,7 @@ func (*FileDescriptorSet) ProtoMessage() {} func (x *FileDescriptorSet) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_descriptor_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1291,11 +1289,9 @@ type FileDescriptorProto struct { func (x *FileDescriptorProto) Reset() { *x = FileDescriptorProto{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_descriptor_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_descriptor_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *FileDescriptorProto) String() string { @@ -1306,7 +1302,7 @@ func (*FileDescriptorProto) ProtoMessage() {} func (x *FileDescriptorProto) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_descriptor_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1434,11 +1430,9 @@ type DescriptorProto struct { func (x *DescriptorProto) Reset() { *x = DescriptorProto{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_descriptor_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_descriptor_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *DescriptorProto) String() string { @@ -1449,7 +1443,7 @@ func (*DescriptorProto) ProtoMessage() {} func (x *DescriptorProto) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_descriptor_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1561,11 +1555,9 @@ const ( func (x *ExtensionRangeOptions) Reset() { *x = ExtensionRangeOptions{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_descriptor_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_descriptor_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ExtensionRangeOptions) String() string { @@ -1576,7 +1568,7 @@ func (*ExtensionRangeOptions) ProtoMessage() {} func (x *ExtensionRangeOptions) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_descriptor_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1680,11 +1672,9 @@ type FieldDescriptorProto struct { func (x *FieldDescriptorProto) Reset() { *x = FieldDescriptorProto{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_descriptor_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_descriptor_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *FieldDescriptorProto) String() string { @@ -1695,7 +1685,7 @@ func (*FieldDescriptorProto) ProtoMessage() {} func (x *FieldDescriptorProto) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_descriptor_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1799,11 +1789,9 @@ type OneofDescriptorProto struct { func (x *OneofDescriptorProto) Reset() { *x = OneofDescriptorProto{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_descriptor_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_descriptor_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *OneofDescriptorProto) String() string { @@ -1814,7 +1802,7 @@ func (*OneofDescriptorProto) ProtoMessage() {} func (x *OneofDescriptorProto) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_descriptor_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1863,11 +1851,9 @@ type EnumDescriptorProto struct { func (x *EnumDescriptorProto) Reset() { *x = EnumDescriptorProto{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_descriptor_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_descriptor_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *EnumDescriptorProto) String() string { @@ -1878,7 +1864,7 @@ func (*EnumDescriptorProto) ProtoMessage() {} func (x *EnumDescriptorProto) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_descriptor_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1941,11 +1927,9 @@ type EnumValueDescriptorProto struct { func (x *EnumValueDescriptorProto) Reset() { *x = EnumValueDescriptorProto{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_descriptor_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_descriptor_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *EnumValueDescriptorProto) String() string { @@ -1956,7 +1940,7 @@ func (*EnumValueDescriptorProto) ProtoMessage() {} func (x *EnumValueDescriptorProto) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_descriptor_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2005,11 +1989,9 @@ type ServiceDescriptorProto struct { func (x *ServiceDescriptorProto) Reset() { *x = ServiceDescriptorProto{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_descriptor_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_descriptor_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ServiceDescriptorProto) String() string { @@ -2020,7 +2002,7 @@ func (*ServiceDescriptorProto) ProtoMessage() {} func (x *ServiceDescriptorProto) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_descriptor_proto_msgTypes[8] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2082,11 +2064,9 @@ const ( func (x *MethodDescriptorProto) Reset() { *x = MethodDescriptorProto{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_descriptor_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_descriptor_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *MethodDescriptorProto) String() string { @@ -2097,7 +2077,7 @@ func (*MethodDescriptorProto) ProtoMessage() {} func (x *MethodDescriptorProto) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_descriptor_proto_msgTypes[9] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2267,11 +2247,9 @@ const ( func (x *FileOptions) Reset() { *x = FileOptions{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_descriptor_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_descriptor_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *FileOptions) String() string { @@ -2282,7 +2260,7 @@ func (*FileOptions) ProtoMessage() {} func (x *FileOptions) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_descriptor_proto_msgTypes[10] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2534,11 +2512,9 @@ const ( func (x *MessageOptions) Reset() { *x = MessageOptions{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_descriptor_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_descriptor_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *MessageOptions) String() string { @@ -2549,7 +2525,7 @@ func (*MessageOptions) ProtoMessage() {} func (x *MessageOptions) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_descriptor_proto_msgTypes[11] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2707,11 +2683,9 @@ const ( func (x *FieldOptions) Reset() { *x = FieldOptions{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_descriptor_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_descriptor_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *FieldOptions) String() string { @@ -2722,7 +2696,7 @@ func (*FieldOptions) ProtoMessage() {} func (x *FieldOptions) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_descriptor_proto_msgTypes[12] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2849,11 +2823,9 @@ type OneofOptions struct { func (x *OneofOptions) Reset() { *x = OneofOptions{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_descriptor_proto_msgTypes[13] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_descriptor_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *OneofOptions) String() string { @@ -2864,7 +2836,7 @@ func (*OneofOptions) ProtoMessage() {} func (x *OneofOptions) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_descriptor_proto_msgTypes[13] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2929,11 +2901,9 @@ const ( func (x *EnumOptions) Reset() { *x = EnumOptions{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_descriptor_proto_msgTypes[14] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_descriptor_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *EnumOptions) String() string { @@ -2944,7 +2914,7 @@ func (*EnumOptions) ProtoMessage() {} func (x *EnumOptions) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_descriptor_proto_msgTypes[14] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3026,11 +2996,9 @@ const ( func (x *EnumValueOptions) Reset() { *x = EnumValueOptions{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_descriptor_proto_msgTypes[15] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_descriptor_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *EnumValueOptions) String() string { @@ -3041,7 +3009,7 @@ func (*EnumValueOptions) ProtoMessage() {} func (x *EnumValueOptions) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_descriptor_proto_msgTypes[15] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3115,11 +3083,9 @@ const ( func (x *ServiceOptions) Reset() { *x = ServiceOptions{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_descriptor_proto_msgTypes[16] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_descriptor_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ServiceOptions) String() string { @@ -3130,7 +3096,7 @@ func (*ServiceOptions) ProtoMessage() {} func (x *ServiceOptions) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_descriptor_proto_msgTypes[16] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3192,11 +3158,9 @@ const ( func (x *MethodOptions) Reset() { *x = MethodOptions{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_descriptor_proto_msgTypes[17] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_descriptor_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *MethodOptions) String() string { @@ -3207,7 +3171,7 @@ func (*MethodOptions) ProtoMessage() {} func (x *MethodOptions) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_descriptor_proto_msgTypes[17] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3274,11 +3238,9 @@ type UninterpretedOption struct { func (x *UninterpretedOption) Reset() { *x = UninterpretedOption{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_descriptor_proto_msgTypes[18] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_descriptor_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *UninterpretedOption) String() string { @@ -3289,7 +3251,7 @@ func (*UninterpretedOption) ProtoMessage() {} func (x *UninterpretedOption) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_descriptor_proto_msgTypes[18] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3375,11 +3337,9 @@ type FeatureSet struct { func (x *FeatureSet) Reset() { *x = FeatureSet{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_descriptor_proto_msgTypes[19] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_descriptor_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *FeatureSet) String() string { @@ -3390,7 +3350,7 @@ func (*FeatureSet) ProtoMessage() {} func (x *FeatureSet) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_descriptor_proto_msgTypes[19] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3467,11 +3427,9 @@ type FeatureSetDefaults struct { func (x *FeatureSetDefaults) Reset() { *x = FeatureSetDefaults{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_descriptor_proto_msgTypes[20] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_descriptor_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *FeatureSetDefaults) String() string { @@ -3482,7 +3440,7 @@ func (*FeatureSetDefaults) ProtoMessage() {} func (x *FeatureSetDefaults) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_descriptor_proto_msgTypes[20] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3578,11 +3536,9 @@ type SourceCodeInfo struct { func (x *SourceCodeInfo) Reset() { *x = SourceCodeInfo{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_descriptor_proto_msgTypes[21] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_descriptor_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *SourceCodeInfo) String() string { @@ -3593,7 +3549,7 @@ func (*SourceCodeInfo) ProtoMessage() {} func (x *SourceCodeInfo) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_descriptor_proto_msgTypes[21] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3630,11 +3586,9 @@ type GeneratedCodeInfo struct { func (x *GeneratedCodeInfo) Reset() { *x = GeneratedCodeInfo{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_descriptor_proto_msgTypes[22] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_descriptor_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *GeneratedCodeInfo) String() string { @@ -3645,7 +3599,7 @@ func (*GeneratedCodeInfo) ProtoMessage() {} func (x *GeneratedCodeInfo) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_descriptor_proto_msgTypes[22] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3679,11 +3633,9 @@ type DescriptorProto_ExtensionRange struct { func (x *DescriptorProto_ExtensionRange) Reset() { *x = DescriptorProto_ExtensionRange{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_descriptor_proto_msgTypes[23] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_descriptor_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *DescriptorProto_ExtensionRange) String() string { @@ -3694,7 +3646,7 @@ func (*DescriptorProto_ExtensionRange) ProtoMessage() {} func (x *DescriptorProto_ExtensionRange) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_descriptor_proto_msgTypes[23] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3744,11 +3696,9 @@ type DescriptorProto_ReservedRange struct { func (x *DescriptorProto_ReservedRange) Reset() { *x = DescriptorProto_ReservedRange{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_descriptor_proto_msgTypes[24] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_descriptor_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *DescriptorProto_ReservedRange) String() string { @@ -3759,7 +3709,7 @@ func (*DescriptorProto_ReservedRange) ProtoMessage() {} func (x *DescriptorProto_ReservedRange) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_descriptor_proto_msgTypes[24] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3813,11 +3763,9 @@ type ExtensionRangeOptions_Declaration struct { func (x *ExtensionRangeOptions_Declaration) Reset() { *x = ExtensionRangeOptions_Declaration{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_descriptor_proto_msgTypes[25] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_descriptor_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ExtensionRangeOptions_Declaration) String() string { @@ -3828,7 +3776,7 @@ func (*ExtensionRangeOptions_Declaration) ProtoMessage() {} func (x *ExtensionRangeOptions_Declaration) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_descriptor_proto_msgTypes[25] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3895,11 +3843,9 @@ type EnumDescriptorProto_EnumReservedRange struct { func (x *EnumDescriptorProto_EnumReservedRange) Reset() { *x = EnumDescriptorProto_EnumReservedRange{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_descriptor_proto_msgTypes[26] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_descriptor_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *EnumDescriptorProto_EnumReservedRange) String() string { @@ -3910,7 +3856,7 @@ func (*EnumDescriptorProto_EnumReservedRange) ProtoMessage() {} func (x *EnumDescriptorProto_EnumReservedRange) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_descriptor_proto_msgTypes[26] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3950,11 +3896,9 @@ type FieldOptions_EditionDefault struct { func (x *FieldOptions_EditionDefault) Reset() { *x = FieldOptions_EditionDefault{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_descriptor_proto_msgTypes[27] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_descriptor_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *FieldOptions_EditionDefault) String() string { @@ -3965,7 +3909,7 @@ func (*FieldOptions_EditionDefault) ProtoMessage() {} func (x *FieldOptions_EditionDefault) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_descriptor_proto_msgTypes[27] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4018,11 +3962,9 @@ type FieldOptions_FeatureSupport struct { func (x *FieldOptions_FeatureSupport) Reset() { *x = FieldOptions_FeatureSupport{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_descriptor_proto_msgTypes[28] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_descriptor_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *FieldOptions_FeatureSupport) String() string { @@ -4033,7 +3975,7 @@ func (*FieldOptions_FeatureSupport) ProtoMessage() {} func (x *FieldOptions_FeatureSupport) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_descriptor_proto_msgTypes[28] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4092,11 +4034,9 @@ type UninterpretedOption_NamePart struct { func (x *UninterpretedOption_NamePart) Reset() { *x = UninterpretedOption_NamePart{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_descriptor_proto_msgTypes[29] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_descriptor_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *UninterpretedOption_NamePart) String() string { @@ -4107,7 +4047,7 @@ func (*UninterpretedOption_NamePart) ProtoMessage() {} func (x *UninterpretedOption_NamePart) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_descriptor_proto_msgTypes[29] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4154,11 +4094,9 @@ type FeatureSetDefaults_FeatureSetEditionDefault struct { func (x *FeatureSetDefaults_FeatureSetEditionDefault) Reset() { *x = FeatureSetDefaults_FeatureSetEditionDefault{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_descriptor_proto_msgTypes[30] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_descriptor_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *FeatureSetDefaults_FeatureSetEditionDefault) String() string { @@ -4169,7 +4107,7 @@ func (*FeatureSetDefaults_FeatureSetEditionDefault) ProtoMessage() {} func (x *FeatureSetDefaults_FeatureSetEditionDefault) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_descriptor_proto_msgTypes[30] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4305,11 +4243,9 @@ type SourceCodeInfo_Location struct { func (x *SourceCodeInfo_Location) Reset() { *x = SourceCodeInfo_Location{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_descriptor_proto_msgTypes[31] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_descriptor_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *SourceCodeInfo_Location) String() string { @@ -4320,7 +4256,7 @@ func (*SourceCodeInfo_Location) ProtoMessage() {} func (x *SourceCodeInfo_Location) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_descriptor_proto_msgTypes[31] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4392,11 +4328,9 @@ type GeneratedCodeInfo_Annotation struct { func (x *GeneratedCodeInfo_Annotation) Reset() { *x = GeneratedCodeInfo_Annotation{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_descriptor_proto_msgTypes[32] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_descriptor_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *GeneratedCodeInfo_Annotation) String() string { @@ -4407,7 +4341,7 @@ func (*GeneratedCodeInfo_Annotation) ProtoMessage() {} func (x *GeneratedCodeInfo_Annotation) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_descriptor_proto_msgTypes[32] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -5385,424 +5319,6 @@ func file_google_protobuf_descriptor_proto_init() { if File_google_protobuf_descriptor_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_google_protobuf_descriptor_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*FileDescriptorSet); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_google_protobuf_descriptor_proto_msgTypes[1].Exporter = func(v any, i int) any { - switch v := v.(*FileDescriptorProto); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_google_protobuf_descriptor_proto_msgTypes[2].Exporter = func(v any, i int) any { - switch v := v.(*DescriptorProto); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_google_protobuf_descriptor_proto_msgTypes[3].Exporter = func(v any, i int) any { - switch v := v.(*ExtensionRangeOptions); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - case 3: - return &v.extensionFields - default: - return nil - } - } - file_google_protobuf_descriptor_proto_msgTypes[4].Exporter = func(v any, i int) any { - switch v := v.(*FieldDescriptorProto); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_google_protobuf_descriptor_proto_msgTypes[5].Exporter = func(v any, i int) any { - switch v := v.(*OneofDescriptorProto); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_google_protobuf_descriptor_proto_msgTypes[6].Exporter = func(v any, i int) any { - switch v := v.(*EnumDescriptorProto); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_google_protobuf_descriptor_proto_msgTypes[7].Exporter = func(v any, i int) any { - switch v := v.(*EnumValueDescriptorProto); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_google_protobuf_descriptor_proto_msgTypes[8].Exporter = func(v any, i int) any { - switch v := v.(*ServiceDescriptorProto); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_google_protobuf_descriptor_proto_msgTypes[9].Exporter = func(v any, i int) any { - switch v := v.(*MethodDescriptorProto); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_google_protobuf_descriptor_proto_msgTypes[10].Exporter = func(v any, i int) any { - switch v := v.(*FileOptions); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - case 3: - return &v.extensionFields - default: - return nil - } - } - file_google_protobuf_descriptor_proto_msgTypes[11].Exporter = func(v any, i int) any { - switch v := v.(*MessageOptions); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - case 3: - return &v.extensionFields - default: - return nil - } - } - file_google_protobuf_descriptor_proto_msgTypes[12].Exporter = func(v any, i int) any { - switch v := v.(*FieldOptions); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - case 3: - return &v.extensionFields - default: - return nil - } - } - file_google_protobuf_descriptor_proto_msgTypes[13].Exporter = func(v any, i int) any { - switch v := v.(*OneofOptions); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - case 3: - return &v.extensionFields - default: - return nil - } - } - file_google_protobuf_descriptor_proto_msgTypes[14].Exporter = func(v any, i int) any { - switch v := v.(*EnumOptions); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - case 3: - return &v.extensionFields - default: - return nil - } - } - file_google_protobuf_descriptor_proto_msgTypes[15].Exporter = func(v any, i int) any { - switch v := v.(*EnumValueOptions); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - case 3: - return &v.extensionFields - default: - return nil - } - } - file_google_protobuf_descriptor_proto_msgTypes[16].Exporter = func(v any, i int) any { - switch v := v.(*ServiceOptions); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - case 3: - return &v.extensionFields - default: - return nil - } - } - file_google_protobuf_descriptor_proto_msgTypes[17].Exporter = func(v any, i int) any { - switch v := v.(*MethodOptions); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - case 3: - return &v.extensionFields - default: - return nil - } - } - file_google_protobuf_descriptor_proto_msgTypes[18].Exporter = func(v any, i int) any { - switch v := v.(*UninterpretedOption); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_google_protobuf_descriptor_proto_msgTypes[19].Exporter = func(v any, i int) any { - switch v := v.(*FeatureSet); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - case 3: - return &v.extensionFields - default: - return nil - } - } - file_google_protobuf_descriptor_proto_msgTypes[20].Exporter = func(v any, i int) any { - switch v := v.(*FeatureSetDefaults); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_google_protobuf_descriptor_proto_msgTypes[21].Exporter = func(v any, i int) any { - switch v := v.(*SourceCodeInfo); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_google_protobuf_descriptor_proto_msgTypes[22].Exporter = func(v any, i int) any { - switch v := v.(*GeneratedCodeInfo); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_google_protobuf_descriptor_proto_msgTypes[23].Exporter = func(v any, i int) any { - switch v := v.(*DescriptorProto_ExtensionRange); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_google_protobuf_descriptor_proto_msgTypes[24].Exporter = func(v any, i int) any { - switch v := v.(*DescriptorProto_ReservedRange); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_google_protobuf_descriptor_proto_msgTypes[25].Exporter = func(v any, i int) any { - switch v := v.(*ExtensionRangeOptions_Declaration); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_google_protobuf_descriptor_proto_msgTypes[26].Exporter = func(v any, i int) any { - switch v := v.(*EnumDescriptorProto_EnumReservedRange); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_google_protobuf_descriptor_proto_msgTypes[27].Exporter = func(v any, i int) any { - switch v := v.(*FieldOptions_EditionDefault); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_google_protobuf_descriptor_proto_msgTypes[28].Exporter = func(v any, i int) any { - switch v := v.(*FieldOptions_FeatureSupport); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_google_protobuf_descriptor_proto_msgTypes[29].Exporter = func(v any, i int) any { - switch v := v.(*UninterpretedOption_NamePart); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_google_protobuf_descriptor_proto_msgTypes[30].Exporter = func(v any, i int) any { - switch v := v.(*FeatureSetDefaults_FeatureSetEditionDefault); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_google_protobuf_descriptor_proto_msgTypes[31].Exporter = func(v any, i int) any { - switch v := v.(*SourceCodeInfo_Location); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_google_protobuf_descriptor_proto_msgTypes[32].Exporter = func(v any, i int) any { - switch v := v.(*GeneratedCodeInfo_Annotation); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/vendor/google.golang.org/protobuf/types/gofeaturespb/go_features.pb.go b/vendor/google.golang.org/protobuf/types/gofeaturespb/go_features.pb.go index a2ca940c50..c7e860fcd6 100644 --- a/vendor/google.golang.org/protobuf/types/gofeaturespb/go_features.pb.go +++ b/vendor/google.golang.org/protobuf/types/gofeaturespb/go_features.pb.go @@ -29,11 +29,9 @@ type GoFeatures struct { func (x *GoFeatures) Reset() { *x = GoFeatures{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_go_features_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_go_features_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *GoFeatures) String() string { @@ -44,7 +42,7 @@ func (*GoFeatures) ProtoMessage() {} func (x *GoFeatures) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_go_features_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -145,20 +143,6 @@ func file_google_protobuf_go_features_proto_init() { if File_google_protobuf_go_features_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_google_protobuf_go_features_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*GoFeatures); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/vendor/google.golang.org/protobuf/types/known/anypb/any.pb.go b/vendor/google.golang.org/protobuf/types/known/anypb/any.pb.go index 7172b43d38..87da199a38 100644 --- a/vendor/google.golang.org/protobuf/types/known/anypb/any.pb.go +++ b/vendor/google.golang.org/protobuf/types/known/anypb/any.pb.go @@ -368,11 +368,9 @@ func (x *Any) UnmarshalNew() (proto.Message, error) { func (x *Any) Reset() { *x = Any{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_any_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_any_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Any) String() string { @@ -383,7 +381,7 @@ func (*Any) ProtoMessage() {} func (x *Any) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_any_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -461,20 +459,6 @@ func file_google_protobuf_any_proto_init() { if File_google_protobuf_any_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_google_protobuf_any_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*Any); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/vendor/google.golang.org/protobuf/types/known/durationpb/duration.pb.go b/vendor/google.golang.org/protobuf/types/known/durationpb/duration.pb.go index 1b71bcd910..b99d4d2410 100644 --- a/vendor/google.golang.org/protobuf/types/known/durationpb/duration.pb.go +++ b/vendor/google.golang.org/protobuf/types/known/durationpb/duration.pb.go @@ -245,11 +245,9 @@ func (x *Duration) check() uint { func (x *Duration) Reset() { *x = Duration{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_duration_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_duration_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Duration) String() string { @@ -260,7 +258,7 @@ func (*Duration) ProtoMessage() {} func (x *Duration) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_duration_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -339,20 +337,6 @@ func file_google_protobuf_duration_proto_init() { if File_google_protobuf_duration_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_google_protobuf_duration_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*Duration); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/vendor/google.golang.org/protobuf/types/known/emptypb/empty.pb.go b/vendor/google.golang.org/protobuf/types/known/emptypb/empty.pb.go index d87b4fb828..1761bc9c69 100644 --- a/vendor/google.golang.org/protobuf/types/known/emptypb/empty.pb.go +++ b/vendor/google.golang.org/protobuf/types/known/emptypb/empty.pb.go @@ -55,11 +55,9 @@ type Empty struct { func (x *Empty) Reset() { *x = Empty{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_empty_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_empty_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Empty) String() string { @@ -70,7 +68,7 @@ func (*Empty) ProtoMessage() {} func (x *Empty) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_empty_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -131,20 +129,6 @@ func file_google_protobuf_empty_proto_init() { if File_google_protobuf_empty_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_google_protobuf_empty_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*Empty); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/vendor/google.golang.org/protobuf/types/known/fieldmaskpb/field_mask.pb.go b/vendor/google.golang.org/protobuf/types/known/fieldmaskpb/field_mask.pb.go index ac1e91bb6d..19de8d371f 100644 --- a/vendor/google.golang.org/protobuf/types/known/fieldmaskpb/field_mask.pb.go +++ b/vendor/google.golang.org/protobuf/types/known/fieldmaskpb/field_mask.pb.go @@ -467,11 +467,9 @@ func rangeFields(path string, f func(field string) bool) bool { func (x *FieldMask) Reset() { *x = FieldMask{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_field_mask_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_field_mask_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *FieldMask) String() string { @@ -482,7 +480,7 @@ func (*FieldMask) ProtoMessage() {} func (x *FieldMask) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_field_mask_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -553,20 +551,6 @@ func file_google_protobuf_field_mask_proto_init() { if File_google_protobuf_field_mask_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_google_protobuf_field_mask_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*FieldMask); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/vendor/google.golang.org/protobuf/types/known/structpb/struct.pb.go b/vendor/google.golang.org/protobuf/types/known/structpb/struct.pb.go index d45361cbc7..8f206a6611 100644 --- a/vendor/google.golang.org/protobuf/types/known/structpb/struct.pb.go +++ b/vendor/google.golang.org/protobuf/types/known/structpb/struct.pb.go @@ -120,6 +120,7 @@ package structpb import ( base64 "encoding/base64" + json "encoding/json" protojson "google.golang.org/protobuf/encoding/protojson" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" @@ -233,11 +234,9 @@ func (x *Struct) UnmarshalJSON(b []byte) error { func (x *Struct) Reset() { *x = Struct{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_struct_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_struct_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Struct) String() string { @@ -248,7 +247,7 @@ func (*Struct) ProtoMessage() {} func (x *Struct) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_struct_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -296,19 +295,20 @@ type Value struct { // NewValue constructs a Value from a general-purpose Go interface. // -// ╔════════════════════════╤════════════════════════════════════════════╗ -// ║ Go type │ Conversion ║ -// ╠════════════════════════╪════════════════════════════════════════════╣ -// ║ nil │ stored as NullValue ║ -// ║ bool │ stored as BoolValue ║ -// ║ int, int32, int64 │ stored as NumberValue ║ -// ║ uint, uint32, uint64 │ stored as NumberValue ║ -// ║ float32, float64 │ stored as NumberValue ║ -// ║ string │ stored as StringValue; must be valid UTF-8 ║ -// ║ []byte │ stored as StringValue; base64-encoded ║ -// ║ map[string]any │ stored as StructValue ║ -// ║ []any │ stored as ListValue ║ -// ╚════════════════════════╧════════════════════════════════════════════╝ +// ╔═══════════════════════════════════════╤════════════════════════════════════════════╗ +// ║ Go type │ Conversion ║ +// ╠═══════════════════════════════════════╪════════════════════════════════════════════╣ +// ║ nil │ stored as NullValue ║ +// ║ bool │ stored as BoolValue ║ +// ║ int, int8, int16, int32, int64 │ stored as NumberValue ║ +// ║ uint, uint8, uint16, uint32, uint64 │ stored as NumberValue ║ +// ║ float32, float64 │ stored as NumberValue ║ +// ║ json.Number │ stored as NumberValue ║ +// ║ string │ stored as StringValue; must be valid UTF-8 ║ +// ║ []byte │ stored as StringValue; base64-encoded ║ +// ║ map[string]any │ stored as StructValue ║ +// ║ []any │ stored as ListValue ║ +// ╚═══════════════════════════════════════╧════════════════════════════════════════════╝ // // When converting an int64 or uint64 to a NumberValue, numeric precision loss // is possible since they are stored as a float64. @@ -320,12 +320,20 @@ func NewValue(v any) (*Value, error) { return NewBoolValue(v), nil case int: return NewNumberValue(float64(v)), nil + case int8: + return NewNumberValue(float64(v)), nil + case int16: + return NewNumberValue(float64(v)), nil case int32: return NewNumberValue(float64(v)), nil case int64: return NewNumberValue(float64(v)), nil case uint: return NewNumberValue(float64(v)), nil + case uint8: + return NewNumberValue(float64(v)), nil + case uint16: + return NewNumberValue(float64(v)), nil case uint32: return NewNumberValue(float64(v)), nil case uint64: @@ -334,6 +342,12 @@ func NewValue(v any) (*Value, error) { return NewNumberValue(float64(v)), nil case float64: return NewNumberValue(float64(v)), nil + case json.Number: + n, err := v.Float64() + if err != nil { + return nil, protoimpl.X.NewError("invalid number format %q, expected a float64: %v", v, err) + } + return NewNumberValue(n), nil case string: if !utf8.ValidString(v) { return nil, protoimpl.X.NewError("invalid UTF-8 in string: %q", v) @@ -441,11 +455,9 @@ func (x *Value) UnmarshalJSON(b []byte) error { func (x *Value) Reset() { *x = Value{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_struct_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_struct_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Value) String() string { @@ -456,7 +468,7 @@ func (*Value) ProtoMessage() {} func (x *Value) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_struct_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -613,11 +625,9 @@ func (x *ListValue) UnmarshalJSON(b []byte) error { func (x *ListValue) Reset() { *x = ListValue{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_struct_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_struct_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ListValue) String() string { @@ -628,7 +638,7 @@ func (*ListValue) ProtoMessage() {} func (x *ListValue) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_struct_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -742,44 +752,6 @@ func file_google_protobuf_struct_proto_init() { if File_google_protobuf_struct_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_google_protobuf_struct_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*Struct); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_google_protobuf_struct_proto_msgTypes[1].Exporter = func(v any, i int) any { - switch v := v.(*Value); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_google_protobuf_struct_proto_msgTypes[2].Exporter = func(v any, i int) any { - switch v := v.(*ListValue); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } file_google_protobuf_struct_proto_msgTypes[1].OneofWrappers = []any{ (*Value_NullValue)(nil), (*Value_NumberValue)(nil), diff --git a/vendor/google.golang.org/protobuf/types/known/timestamppb/timestamp.pb.go b/vendor/google.golang.org/protobuf/types/known/timestamppb/timestamp.pb.go index 83a5a645b0..0d20722d70 100644 --- a/vendor/google.golang.org/protobuf/types/known/timestamppb/timestamp.pb.go +++ b/vendor/google.golang.org/protobuf/types/known/timestamppb/timestamp.pb.go @@ -254,11 +254,9 @@ func (x *Timestamp) check() uint { func (x *Timestamp) Reset() { *x = Timestamp{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_timestamp_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_timestamp_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Timestamp) String() string { @@ -269,7 +267,7 @@ func (*Timestamp) ProtoMessage() {} func (x *Timestamp) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_timestamp_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -348,20 +346,6 @@ func file_google_protobuf_timestamp_proto_init() { if File_google_protobuf_timestamp_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_google_protobuf_timestamp_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*Timestamp); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/vendor/google.golang.org/protobuf/types/known/wrapperspb/wrappers.pb.go b/vendor/google.golang.org/protobuf/types/known/wrapperspb/wrappers.pb.go index e473f826aa..006060e569 100644 --- a/vendor/google.golang.org/protobuf/types/known/wrapperspb/wrappers.pb.go +++ b/vendor/google.golang.org/protobuf/types/known/wrapperspb/wrappers.pb.go @@ -69,11 +69,9 @@ func Double(v float64) *DoubleValue { func (x *DoubleValue) Reset() { *x = DoubleValue{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_wrappers_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_wrappers_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *DoubleValue) String() string { @@ -84,7 +82,7 @@ func (*DoubleValue) ProtoMessage() {} func (x *DoubleValue) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_wrappers_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -125,11 +123,9 @@ func Float(v float32) *FloatValue { func (x *FloatValue) Reset() { *x = FloatValue{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_wrappers_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_wrappers_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *FloatValue) String() string { @@ -140,7 +136,7 @@ func (*FloatValue) ProtoMessage() {} func (x *FloatValue) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_wrappers_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -181,11 +177,9 @@ func Int64(v int64) *Int64Value { func (x *Int64Value) Reset() { *x = Int64Value{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_wrappers_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_wrappers_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Int64Value) String() string { @@ -196,7 +190,7 @@ func (*Int64Value) ProtoMessage() {} func (x *Int64Value) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_wrappers_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -237,11 +231,9 @@ func UInt64(v uint64) *UInt64Value { func (x *UInt64Value) Reset() { *x = UInt64Value{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_wrappers_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_wrappers_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *UInt64Value) String() string { @@ -252,7 +244,7 @@ func (*UInt64Value) ProtoMessage() {} func (x *UInt64Value) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_wrappers_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -293,11 +285,9 @@ func Int32(v int32) *Int32Value { func (x *Int32Value) Reset() { *x = Int32Value{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_wrappers_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_wrappers_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Int32Value) String() string { @@ -308,7 +298,7 @@ func (*Int32Value) ProtoMessage() {} func (x *Int32Value) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_wrappers_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -349,11 +339,9 @@ func UInt32(v uint32) *UInt32Value { func (x *UInt32Value) Reset() { *x = UInt32Value{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_wrappers_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_wrappers_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *UInt32Value) String() string { @@ -364,7 +352,7 @@ func (*UInt32Value) ProtoMessage() {} func (x *UInt32Value) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_wrappers_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -405,11 +393,9 @@ func Bool(v bool) *BoolValue { func (x *BoolValue) Reset() { *x = BoolValue{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_wrappers_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_wrappers_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *BoolValue) String() string { @@ -420,7 +406,7 @@ func (*BoolValue) ProtoMessage() {} func (x *BoolValue) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_wrappers_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -461,11 +447,9 @@ func String(v string) *StringValue { func (x *StringValue) Reset() { *x = StringValue{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_wrappers_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_wrappers_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *StringValue) String() string { @@ -476,7 +460,7 @@ func (*StringValue) ProtoMessage() {} func (x *StringValue) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_wrappers_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -517,11 +501,9 @@ func Bytes(v []byte) *BytesValue { func (x *BytesValue) Reset() { *x = BytesValue{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_wrappers_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_wrappers_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *BytesValue) String() string { @@ -532,7 +514,7 @@ func (*BytesValue) ProtoMessage() {} func (x *BytesValue) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_wrappers_proto_msgTypes[8] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -629,116 +611,6 @@ func file_google_protobuf_wrappers_proto_init() { if File_google_protobuf_wrappers_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_google_protobuf_wrappers_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*DoubleValue); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_google_protobuf_wrappers_proto_msgTypes[1].Exporter = func(v any, i int) any { - switch v := v.(*FloatValue); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_google_protobuf_wrappers_proto_msgTypes[2].Exporter = func(v any, i int) any { - switch v := v.(*Int64Value); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_google_protobuf_wrappers_proto_msgTypes[3].Exporter = func(v any, i int) any { - switch v := v.(*UInt64Value); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_google_protobuf_wrappers_proto_msgTypes[4].Exporter = func(v any, i int) any { - switch v := v.(*Int32Value); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_google_protobuf_wrappers_proto_msgTypes[5].Exporter = func(v any, i int) any { - switch v := v.(*UInt32Value); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_google_protobuf_wrappers_proto_msgTypes[6].Exporter = func(v any, i int) any { - switch v := v.(*BoolValue); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_google_protobuf_wrappers_proto_msgTypes[7].Exporter = func(v any, i int) any { - switch v := v.(*StringValue); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_google_protobuf_wrappers_proto_msgTypes[8].Exporter = func(v any, i int) any { - switch v := v.(*BytesValue); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/vendor/helm.sh/helm/v3/internal/resolver/resolver.go b/vendor/helm.sh/helm/v3/internal/resolver/resolver.go index c5fc636433..b6f45da9e2 100644 --- a/vendor/helm.sh/helm/v3/internal/resolver/resolver.go +++ b/vendor/helm.sh/helm/v3/internal/resolver/resolver.go @@ -172,7 +172,7 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string Repository: d.Repository, Version: version, } - // The version are already sorted and hence the first one to satisfy the constraint is used + // The versions are already sorted and hence the first one to satisfy the constraint is used for _, ver := range vs { v, err := semver.NewVersion(ver.Version) // OCI does not need URLs diff --git a/vendor/helm.sh/helm/v3/internal/third_party/dep/fs/fs.go b/vendor/helm.sh/helm/v3/internal/third_party/dep/fs/fs.go index 4e4eacc60d..d29bb5f871 100644 --- a/vendor/helm.sh/helm/v3/internal/third_party/dep/fs/fs.go +++ b/vendor/helm.sh/helm/v3/internal/third_party/dep/fs/fs.go @@ -260,7 +260,7 @@ func fixLongPath(path string) string { // minus 12)." Since MAX_PATH is 260, 260 - 12 = 248. // // The MSDN docs appear to say that a normal path that is 248 bytes long - // will work; empirically the path must be less then 248 bytes long. + // will work; empirically the path must be less than 248 bytes long. if len(path) < 248 { // Don't fix. (This is how Go 1.7 and earlier worked, // not automatically generating the \\?\ form) diff --git a/vendor/helm.sh/helm/v3/internal/tlsutil/tls.go b/vendor/helm.sh/helm/v3/internal/tlsutil/tls.go index dc832ed80e..7cd1dace96 100644 --- a/vendor/helm.sh/helm/v3/internal/tlsutil/tls.go +++ b/vendor/helm.sh/helm/v3/internal/tlsutil/tls.go @@ -65,7 +65,7 @@ func CertPoolFromFile(filename string) (*x509.CertPool, error) { return cp, nil } -// CertFromFilePair returns an tls.Certificate containing the +// CertFromFilePair returns a tls.Certificate containing the // certificates public/private key pair from a pair of given PEM-encoded files. // Returns an error if the file could not be read, a certificate could not // be parsed, or if the file does not contain any certificates diff --git a/vendor/helm.sh/helm/v3/pkg/action/install.go b/vendor/helm.sh/helm/v3/pkg/action/install.go index f0292a0a3f..7ca40c88aa 100644 --- a/vendor/helm.sh/helm/v3/pkg/action/install.go +++ b/vendor/helm.sh/helm/v3/pkg/action/install.go @@ -55,7 +55,7 @@ import ( "helm.sh/helm/v3/pkg/storage/driver" ) -// NOTESFILE_SUFFIX that we want to treat special. It goes through the templating engine +// notesFileSuffix that we want to treat special. It goes through the templating engine // but it's not a yaml file (resource) hence can't have hooks, etc. And the user actually // wants to see this file after rendering in the status command. However, it must be a suffix // since there can be filepath in front of it. @@ -307,7 +307,7 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma } if driver.ContainsSystemLabels(i.Labels) { - return nil, fmt.Errorf("user suplied labels contains system reserved label name. System labels: %+v", driver.GetSystemLabels()) + return nil, fmt.Errorf("user supplied labels contains system reserved label name. System labels: %+v", driver.GetSystemLabels()) } rel := i.createRelease(chrt, vals, i.Labels) @@ -389,7 +389,7 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma } } - // If Replace is true, we need to supercede the last release. + // If Replace is true, we need to supersede the last release. if i.Replace { if err := i.replaceRelease(rel); err != nil { return nil, err @@ -631,7 +631,7 @@ func createOrOpenFile(filename string, append bool) (*os.File, error) { return os.Create(filename) } -// check if the directory exists to create file. creates if don't exists +// check if the directory exists to create file. creates if doesn't exist func ensureDirectoryForFile(file string) error { baseDir := path.Dir(file) _, err := os.Stat(baseDir) diff --git a/vendor/helm.sh/helm/v3/pkg/action/upgrade.go b/vendor/helm.sh/helm/v3/pkg/action/upgrade.go index 15bdae8da5..a08d68495f 100644 --- a/vendor/helm.sh/helm/v3/pkg/action/upgrade.go +++ b/vendor/helm.sh/helm/v3/pkg/action/upgrade.go @@ -279,7 +279,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin } if driver.ContainsSystemLabels(u.Labels) { - return nil, nil, fmt.Errorf("user suplied labels contains system reserved label name. System labels: %+v", driver.GetSystemLabels()) + return nil, nil, fmt.Errorf("user supplied labels contains system reserved label name. System labels: %+v", driver.GetSystemLabels()) } // Store an upgraded release. diff --git a/vendor/helm.sh/helm/v3/pkg/chart/loader/archive.go b/vendor/helm.sh/helm/v3/pkg/chart/loader/archive.go index 196e5f81d6..8bb5493465 100644 --- a/vendor/helm.sh/helm/v3/pkg/chart/loader/archive.go +++ b/vendor/helm.sh/helm/v3/pkg/chart/loader/archive.go @@ -101,7 +101,7 @@ func ensureArchive(name string, raw *os.File) error { return nil } -// isGZipApplication checks whether the achieve is of the application/x-gzip type. +// isGZipApplication checks whether the archive is of the application/x-gzip type. func isGZipApplication(data []byte) bool { sig := []byte("\x1F\x8B\x08") return bytes.HasPrefix(data, sig) diff --git a/vendor/helm.sh/helm/v3/pkg/chartutil/dependencies.go b/vendor/helm.sh/helm/v3/pkg/chartutil/dependencies.go index 205d99e093..36a3419272 100644 --- a/vendor/helm.sh/helm/v3/pkg/chartutil/dependencies.go +++ b/vendor/helm.sh/helm/v3/pkg/chartutil/dependencies.go @@ -137,7 +137,7 @@ func processDependencyEnabled(c *chart.Chart, v map[string]interface{}, path str // If any dependency is not a part of Chart.yaml // then this should be added to chartDependencies. // However, if the dependency is already specified in Chart.yaml - // we should not add it, as it would be anyways processed from Chart.yaml + // we should not add it, as it would be processed from Chart.yaml anyway. Loop: for _, existing := range c.Dependencies() { diff --git a/vendor/helm.sh/helm/v3/pkg/cli/output/output.go b/vendor/helm.sh/helm/v3/pkg/cli/output/output.go index a46c977ad9..01649c812f 100644 --- a/vendor/helm.sh/helm/v3/pkg/cli/output/output.go +++ b/vendor/helm.sh/helm/v3/pkg/cli/output/output.go @@ -73,7 +73,7 @@ func (o Format) Write(out io.Writer, w Writer) error { } // ParseFormat takes a raw string and returns the matching Format. -// If the format does not exists, ErrInvalidFormatType is returned +// If the format does not exist, ErrInvalidFormatType is returned func ParseFormat(s string) (out Format, err error) { switch s { case Table.String(): diff --git a/vendor/helm.sh/helm/v3/pkg/downloader/manager.go b/vendor/helm.sh/helm/v3/pkg/downloader/manager.go index d5340575d1..ec4056d275 100644 --- a/vendor/helm.sh/helm/v3/pkg/downloader/manager.go +++ b/vendor/helm.sh/helm/v3/pkg/downloader/manager.go @@ -173,7 +173,7 @@ func (m *Manager) Update() error { // has some information about them and, when possible, the index files // locally. // TODO(mattfarina): Repositories should be explicitly added by end users - // rather than automattic. In Helm v4 require users to add repositories. They + // rather than automatic. In Helm v4 require users to add repositories. They // should have to add them in order to make sure they are aware of the // repositories and opt-in to any locations, for security. repoNames, err = m.ensureMissingRepos(repoNames, req) diff --git a/vendor/helm.sh/helm/v3/pkg/engine/engine.go b/vendor/helm.sh/helm/v3/pkg/engine/engine.go index 058cfa7493..df3a600a39 100644 --- a/vendor/helm.sh/helm/v3/pkg/engine/engine.go +++ b/vendor/helm.sh/helm/v3/pkg/engine/engine.go @@ -169,7 +169,7 @@ func tplFun(parent *template.Template, includedNames map[string]int, strict bool }) // We need a .New template, as template text which is just blanks - // or comments after parsing out defines just addes new named + // or comments after parsing out defines just adds new named // template definitions without changing the main template. // https://pkg.go.dev/text/template#Template.Parse // Use the parent's name for lack of a better way to identify the tpl diff --git a/vendor/helm.sh/helm/v3/pkg/engine/lookup_func.go b/vendor/helm.sh/helm/v3/pkg/engine/lookup_func.go index 86a7d698ca..75e85098d1 100644 --- a/vendor/helm.sh/helm/v3/pkg/engine/lookup_func.go +++ b/vendor/helm.sh/helm/v3/pkg/engine/lookup_func.go @@ -131,7 +131,7 @@ func getAPIResourceForGVK(gvk schema.GroupVersionKind, config *rest.Config) (met return res, err } for _, resource := range resList.APIResources { - // if a resource contains a "/" it's referencing a subresource. we don't support suberesource for now. + // if a resource contains a "/" it's referencing a subresource. we don't support subresource for now. if resource.Kind == gvk.Kind && !strings.Contains(resource.Name, "/") { res = resource res.Group = gvk.Group diff --git a/vendor/helm.sh/helm/v3/pkg/helmpath/lazypath.go b/vendor/helm.sh/helm/v3/pkg/helmpath/lazypath.go index 22d7bf0a1b..6b4f1fc770 100644 --- a/vendor/helm.sh/helm/v3/pkg/helmpath/lazypath.go +++ b/vendor/helm.sh/helm/v3/pkg/helmpath/lazypath.go @@ -34,7 +34,7 @@ const ( DataHomeEnvVar = "HELM_DATA_HOME" ) -// lazypath is an lazy-loaded path buffer for the XDG base directory specification. +// lazypath is a lazy-loaded path buffer for the XDG base directory specification. type lazypath string func (l lazypath) path(helmEnvVar, xdgEnvVar string, defaultFn func() string, elem ...string) string { diff --git a/vendor/helm.sh/helm/v3/pkg/ignore/doc.go b/vendor/helm.sh/helm/v3/pkg/ignore/doc.go index 5245d410ee..1f5e918477 100644 --- a/vendor/helm.sh/helm/v3/pkg/ignore/doc.go +++ b/vendor/helm.sh/helm/v3/pkg/ignore/doc.go @@ -26,7 +26,7 @@ The formatting rules are as follows: - Parsing is line-by-line - Empty lines are ignored - - Lines the begin with # (comments) will be ignored + - Lines that begin with # (comments) will be ignored - Leading and trailing spaces are always ignored - Inline comments are NOT supported ('foo* # Any foo' does not contain a comment) - There is no support for multi-line patterns diff --git a/vendor/helm.sh/helm/v3/pkg/kube/client.go b/vendor/helm.sh/helm/v3/pkg/kube/client.go index 9df833a434..d979fd22cd 100644 --- a/vendor/helm.sh/helm/v3/pkg/kube/client.go +++ b/vendor/helm.sh/helm/v3/pkg/kube/client.go @@ -124,7 +124,7 @@ func (c *Client) getKubeClient() (*kubernetes.Clientset, error) { func (c *Client) IsReachable() error { client, err := c.getKubeClient() if err == genericclioptions.ErrEmptyConfig { - // re-replace kubernetes ErrEmptyConfig error with a friendy error + // re-replace kubernetes ErrEmptyConfig error with a friendly error // moar workarounds for Kubernetes API breaking. return errors.New("Kubernetes cluster unreachable") } @@ -635,7 +635,7 @@ func createPatch(target *resource.Info, current runtime.Object) ([]byte, types.P // Get a versioned object versionedObject := AsVersioned(target) - // Unstructured objects, such as CRDs, may not have an not registered error + // Unstructured objects, such as CRDs, may not have a not registered error // returned from ConvertToVersion. Anything that's unstructured should // use the jsonpatch.CreateMergePatch. Strategic Merge Patch is not supported // on objects like CRDs. diff --git a/vendor/helm.sh/helm/v3/pkg/kube/ready.go b/vendor/helm.sh/helm/v3/pkg/kube/ready.go index b2d26ba761..55c4a39bf1 100644 --- a/vendor/helm.sh/helm/v3/pkg/kube/ready.go +++ b/vendor/helm.sh/helm/v3/pkg/kube/ready.go @@ -426,7 +426,7 @@ func (c *ReadyChecker) statefulSetReady(sts *appsv1.StatefulSet) bool { return false } // This check only makes sense when all partitions are being upgraded otherwise during a - // partioned rolling upgrade, this condition will never evaluate to true, leading to + // partitioned rolling upgrade, this condition will never evaluate to true, leading to // error. if partition == 0 && sts.Status.CurrentRevision != sts.Status.UpdateRevision { c.log("StatefulSet is not ready: %s/%s. currentRevision %s does not yet match updateRevision %s", sts.Namespace, sts.Name, sts.Status.CurrentRevision, sts.Status.UpdateRevision) diff --git a/vendor/helm.sh/helm/v3/pkg/registry/util.go b/vendor/helm.sh/helm/v3/pkg/registry/util.go index 45fbdd0b51..727cdae033 100644 --- a/vendor/helm.sh/helm/v3/pkg/registry/util.go +++ b/vendor/helm.sh/helm/v3/pkg/registry/util.go @@ -65,8 +65,7 @@ func GetTagMatchingVersionOrConstraint(tags []string, versionString string) (str // If string is empty, set wildcard constraint constraint, _ = semver.NewConstraint("*") } else { - // when customer input exact version, check whether have exact match - // one first + // when customer inputs specific version, check whether there's an exact match first for _, v := range tags { if versionString == v { return v, nil diff --git a/vendor/helm.sh/helm/v3/pkg/release/status.go b/vendor/helm.sh/helm/v3/pkg/release/status.go index e0e3ed62a9..edd27a5f14 100644 --- a/vendor/helm.sh/helm/v3/pkg/release/status.go +++ b/vendor/helm.sh/helm/v3/pkg/release/status.go @@ -31,13 +31,13 @@ const ( StatusSuperseded Status = "superseded" // StatusFailed indicates that the release was not successfully deployed. StatusFailed Status = "failed" - // StatusUninstalling indicates that a uninstall operation is underway. + // StatusUninstalling indicates that an uninstall operation is underway. StatusUninstalling Status = "uninstalling" // StatusPendingInstall indicates that an install operation is underway. StatusPendingInstall Status = "pending-install" // StatusPendingUpgrade indicates that an upgrade operation is underway. StatusPendingUpgrade Status = "pending-upgrade" - // StatusPendingRollback indicates that an rollback operation is underway. + // StatusPendingRollback indicates that a rollback operation is underway. StatusPendingRollback Status = "pending-rollback" ) diff --git a/vendor/helm.sh/helm/v3/pkg/repo/index.go b/vendor/helm.sh/helm/v3/pkg/repo/index.go index 40b11c5cf2..e1ce3c62dd 100644 --- a/vendor/helm.sh/helm/v3/pkg/repo/index.go +++ b/vendor/helm.sh/helm/v3/pkg/repo/index.go @@ -200,7 +200,7 @@ func (i IndexFile) Get(name, version string) (*ChartVersion, error) { } } - // when customer input exact version, check whether have exact match one first + // when customer inputs specific version, check whether there's an exact match first if len(version) != 0 { for _, ver := range vs { if version == ver.Version { @@ -371,6 +371,8 @@ func loadIndex(data []byte, source string) (*IndexFile, error) { cvs = append(cvs[:idx], cvs[idx+1:]...) } } + // adjust slice to only contain a set of valid versions + i.Entries[name] = cvs } i.SortEntries() if i.APIVersion == "" { @@ -397,7 +399,7 @@ func jsonOrYamlUnmarshal(b []byte, i interface{}) error { // the error isn't important for index loading // // In particular, charts may introduce validations that don't impact repository indexes -// And repository indexes may be generated by older/non-complient software, which doesn't +// And repository indexes may be generated by older/non-compliant software, which doesn't // conform to all validations. func ignoreSkippableChartValidationError(err error) error { verr, ok := err.(chart.ValidationError) diff --git a/vendor/helm.sh/helm/v3/pkg/storage/driver/sql.go b/vendor/helm.sh/helm/v3/pkg/storage/driver/sql.go index 2ef951184b..33bde9b6a4 100644 --- a/vendor/helm.sh/helm/v3/pkg/storage/driver/sql.go +++ b/vendor/helm.sh/helm/v3/pkg/storage/driver/sql.go @@ -72,8 +72,8 @@ const ( // Following limits based on k8s labels limits - https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set const ( - sqlCustomLabelsTableKeyMaxLenght = 253 + 1 + 63 - sqlCustomLabelsTableValueMaxLenght = 63 + sqlCustomLabelsTableKeyMaxLength = 253 + 1 + 63 + sqlCustomLabelsTableValueMaxLength = 63 ) const ( @@ -119,7 +119,7 @@ func (s *SQL) checkAlreadyApplied(migrations []*migrate.Migration) bool { } } - // check if all migrations appliyed + // check if all migrations applied if len(migrationsIDs) != 0 { for id := range migrationsIDs { s.Log("checkAlreadyApplied: find unapplied migration (id: %v)", id) @@ -204,7 +204,7 @@ func (s *SQL) ensureDBSetup() error { CREATE TABLE %s ( %s VARCHAR(64), %s VARCHAR(67), - %s VARCHAR(%d), + %s VARCHAR(%d), %s VARCHAR(%d) ); CREATE INDEX ON %s (%s, %s); @@ -216,9 +216,9 @@ func (s *SQL) ensureDBSetup() error { sqlCustomLabelsTableReleaseKeyColumn, sqlCustomLabelsTableReleaseNamespaceColumn, sqlCustomLabelsTableKeyColumn, - sqlCustomLabelsTableKeyMaxLenght, + sqlCustomLabelsTableKeyMaxLength, sqlCustomLabelsTableValueColumn, - sqlCustomLabelsTableValueMaxLenght, + sqlCustomLabelsTableValueMaxLength, sqlCustomLabelsTableName, sqlCustomLabelsTableReleaseKeyColumn, sqlCustomLabelsTableReleaseNamespaceColumn, diff --git a/vendor/helm.sh/helm/v3/pkg/strvals/parser.go b/vendor/helm.sh/helm/v3/pkg/strvals/parser.go index 2828f20c08..a0e8d66d15 100644 --- a/vendor/helm.sh/helm/v3/pkg/strvals/parser.go +++ b/vendor/helm.sh/helm/v3/pkg/strvals/parser.go @@ -436,7 +436,7 @@ func (t *parser) listItem(list []interface{}, i, nestedNameLevel int) ([]interfa // check for an empty value // read and consume optional spaces until comma or EOF (empty val) or any other char (not empty val) -// comma and spaces are consumed, while any other char is not cosumed +// comma and spaces are consumed, while any other char is not consumed func (t *parser) emptyVal() (bool, error) { for { r, _, e := t.sc.ReadRune() diff --git a/vendor/helm.sh/helm/v3/pkg/time/time.go b/vendor/helm.sh/helm/v3/pkg/time/time.go index 44f3fedfb2..1abe8ae3d8 100644 --- a/vendor/helm.sh/helm/v3/pkg/time/time.go +++ b/vendor/helm.sh/helm/v3/pkg/time/time.go @@ -15,7 +15,7 @@ limitations under the License. */ // Package time contains a wrapper for time.Time in the standard library and -// associated methods. This package mainly exists to workaround an issue in Go +// associated methods. This package mainly exists to work around an issue in Go // where the serializer doesn't omit an empty value for time: // https://github.com/golang/go/issues/11939. As such, this can be removed if a // proposal is ever accepted for Go diff --git a/vendor/k8s.io/kubectl/pkg/util/apply.go b/vendor/k8s.io/kubectl/pkg/util/apply.go new file mode 100644 index 0000000000..77ea593842 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/util/apply.go @@ -0,0 +1,146 @@ +/* +Copyright 2014 The Kubernetes 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 util + +import ( + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime" +) + +var metadataAccessor = meta.NewAccessor() + +// GetOriginalConfiguration retrieves the original configuration of the object +// from the annotation, or nil if no annotation was found. +func GetOriginalConfiguration(obj runtime.Object) ([]byte, error) { + annots, err := metadataAccessor.Annotations(obj) + if err != nil { + return nil, err + } + + if annots == nil { + return nil, nil + } + + original, ok := annots[v1.LastAppliedConfigAnnotation] + if !ok { + return nil, nil + } + + return []byte(original), nil +} + +// SetOriginalConfiguration sets the original configuration of the object +// as the annotation on the object for later use in computing a three way patch. +func setOriginalConfiguration(obj runtime.Object, original []byte) error { + if len(original) < 1 { + return nil + } + + annots, err := metadataAccessor.Annotations(obj) + if err != nil { + return err + } + + if annots == nil { + annots = map[string]string{} + } + + annots[v1.LastAppliedConfigAnnotation] = string(original) + return metadataAccessor.SetAnnotations(obj, annots) +} + +// GetModifiedConfiguration retrieves the modified configuration of the object. +// If annotate is true, it embeds the result as an annotation in the modified +// configuration. If an object was read from the command input, it will use that +// version of the object. Otherwise, it will use the version from the server. +func GetModifiedConfiguration(obj runtime.Object, annotate bool, codec runtime.Encoder) ([]byte, error) { + // First serialize the object without the annotation to prevent recursion, + // then add that serialization to it as the annotation and serialize it again. + var modified []byte + + // Otherwise, use the server side version of the object. + // Get the current annotations from the object. + annots, err := metadataAccessor.Annotations(obj) + if err != nil { + return nil, err + } + + if annots == nil { + annots = map[string]string{} + } + + original := annots[v1.LastAppliedConfigAnnotation] + delete(annots, v1.LastAppliedConfigAnnotation) + if err := metadataAccessor.SetAnnotations(obj, annots); err != nil { + return nil, err + } + + modified, err = runtime.Encode(codec, obj) + if err != nil { + return nil, err + } + + if annotate { + annots[v1.LastAppliedConfigAnnotation] = string(modified) + if err := metadataAccessor.SetAnnotations(obj, annots); err != nil { + return nil, err + } + + modified, err = runtime.Encode(codec, obj) + if err != nil { + return nil, err + } + } + + // Restore the object to its original condition. + annots[v1.LastAppliedConfigAnnotation] = original + if err := metadataAccessor.SetAnnotations(obj, annots); err != nil { + return nil, err + } + + return modified, nil +} + +// updateApplyAnnotation calls CreateApplyAnnotation if the last applied +// configuration annotation is already present. Otherwise, it does nothing. +func updateApplyAnnotation(obj runtime.Object, codec runtime.Encoder) error { + if original, err := GetOriginalConfiguration(obj); err != nil || len(original) <= 0 { + return err + } + return CreateApplyAnnotation(obj, codec) +} + +// CreateApplyAnnotation gets the modified configuration of the object, +// without embedding it again, and then sets it on the object as the annotation. +func CreateApplyAnnotation(obj runtime.Object, codec runtime.Encoder) error { + modified, err := GetModifiedConfiguration(obj, false, codec) + if err != nil { + return err + } + return setOriginalConfiguration(obj, modified) +} + +// CreateOrUpdateAnnotation creates the annotation used by +// kubectl apply only when createAnnotation is true +// Otherwise, only update the annotation when it already exists +func CreateOrUpdateAnnotation(createAnnotation bool, obj runtime.Object, codec runtime.Encoder) error { + if createAnnotation { + return CreateApplyAnnotation(obj, codec) + } + return updateApplyAnnotation(obj, codec) +} diff --git a/vendor/k8s.io/kubectl/pkg/util/pod_port.go b/vendor/k8s.io/kubectl/pkg/util/pod_port.go new file mode 100644 index 0000000000..bcd2c72818 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/util/pod_port.go @@ -0,0 +1,42 @@ +/* +Copyright 2018 The Kubernetes 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 util + +import ( + "fmt" + + "k8s.io/api/core/v1" +) + +// LookupContainerPortNumberByName find containerPort number by its named port name +func LookupContainerPortNumberByName(pod v1.Pod, name string) (int32, error) { + for _, ctr := range pod.Spec.Containers { + for _, ctrportspec := range ctr.Ports { + if ctrportspec.Name == name { + return ctrportspec.ContainerPort, nil + } + } + } + for _, ctr := range pod.Spec.InitContainers { + for _, ctrportspec := range ctr.Ports { + if ctrportspec.Name == name { + return ctrportspec.ContainerPort, nil + } + } + } + return int32(-1), fmt.Errorf("Pod '%s' does not have a named port '%s'", pod.Name, name) +} diff --git a/vendor/k8s.io/kubectl/pkg/util/podutils/podutils.go b/vendor/k8s.io/kubectl/pkg/util/podutils/podutils.go new file mode 100644 index 0000000000..642a6d47a7 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/util/podutils/podutils.go @@ -0,0 +1,251 @@ +/* +Copyright 2014 The Kubernetes 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 podutils + +import ( + "time" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// IsPodAvailable returns true if a pod is available; false otherwise. +// Precondition for an available pod is that it must be ready. On top +// of that, there are two cases when a pod can be considered available: +// 1. minReadySeconds == 0, or +// 2. LastTransitionTime (is set) + minReadySeconds < current time +func IsPodAvailable(pod *corev1.Pod, minReadySeconds int32, now metav1.Time) bool { + if !IsPodReady(pod) { + return false + } + + c := getPodReadyCondition(pod.Status) + minReadySecondsDuration := time.Duration(minReadySeconds) * time.Second + if minReadySeconds == 0 || !c.LastTransitionTime.IsZero() && c.LastTransitionTime.Add(minReadySecondsDuration).Before(now.Time) { + return true + } + return false +} + +// IsPodReady returns true if a pod is ready; false otherwise. +func IsPodReady(pod *corev1.Pod) bool { + return isPodReadyConditionTrue(pod.Status) +} + +func isPodDeleting(pod *corev1.Pod) bool { + return pod.DeletionTimestamp != nil +} + +// IsPodReadyConditionTrue returns true if a pod is ready; false otherwise. +func isPodReadyConditionTrue(status corev1.PodStatus) bool { + condition := getPodReadyCondition(status) + return condition != nil && condition.Status == corev1.ConditionTrue +} + +// GetPodReadyCondition extracts the pod ready condition from the given status and returns that. +// Returns nil if the condition is not present. +func getPodReadyCondition(status corev1.PodStatus) *corev1.PodCondition { + _, condition := getPodCondition(&status, corev1.PodReady) + return condition +} + +// GetPodCondition extracts the provided condition from the given status and returns that. +// Returns nil and -1 if the condition is not present, and the index of the located condition. +func getPodCondition(status *corev1.PodStatus, conditionType corev1.PodConditionType) (int, *corev1.PodCondition) { + if status == nil { + return -1, nil + } + return getPodConditionFromList(status.Conditions, conditionType) +} + +// GetPodConditionFromList extracts the provided condition from the given list of condition and +// returns the index of the condition and the condition. Returns -1 and nil if the condition is not present. +func getPodConditionFromList(conditions []corev1.PodCondition, conditionType corev1.PodConditionType) (int, *corev1.PodCondition) { + if conditions == nil { + return -1, nil + } + for i := range conditions { + if conditions[i].Type == conditionType { + return i, &conditions[i] + } + } + return -1, nil +} + +// ByLogging allows custom sorting of pods so the best one can be picked for getting its logs. +type ByLogging []*corev1.Pod + +func (s ByLogging) Len() int { return len(s) } +func (s ByLogging) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +func (s ByLogging) Less(i, j int) bool { + // 1. assigned < unassigned + if s[i].Spec.NodeName != s[j].Spec.NodeName && (len(s[i].Spec.NodeName) == 0 || len(s[j].Spec.NodeName) == 0) { + return len(s[i].Spec.NodeName) > 0 + } + // 2. PodRunning < PodUnknown < PodPending + m := map[corev1.PodPhase]int{corev1.PodRunning: 0, corev1.PodUnknown: 1, corev1.PodPending: 2} + if m[s[i].Status.Phase] != m[s[j].Status.Phase] { + return m[s[i].Status.Phase] < m[s[j].Status.Phase] + } + // 3. ready < not ready + if IsPodReady(s[i]) != IsPodReady(s[j]) { + return IsPodReady(s[i]) + } + // TODO: take availability into account when we push minReadySeconds information from deployment into pods, + // see https://github.com/kubernetes/kubernetes/issues/22065 + // 4. Been ready for more time < less time < empty time + if IsPodReady(s[i]) && IsPodReady(s[j]) && !podReadyTime(s[i]).Equal(podReadyTime(s[j])) { + return afterOrZero(podReadyTime(s[j]), podReadyTime(s[i])) + } + // 5. Pods with containers with higher restart counts < lower restart counts + if maxContainerRestarts(s[i]) != maxContainerRestarts(s[j]) { + return maxContainerRestarts(s[i]) > maxContainerRestarts(s[j]) + } + // 6. older pods < newer pods < empty timestamp pods + if !s[i].CreationTimestamp.Equal(&s[j].CreationTimestamp) { + return afterOrZero(&s[j].CreationTimestamp, &s[i].CreationTimestamp) + } + return false +} + +// ActivePods type allows custom sorting of pods so a controller can pick the best ones to delete. +type ActivePods []*corev1.Pod + +func (s ActivePods) Len() int { return len(s) } +func (s ActivePods) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +func (s ActivePods) Less(i, j int) bool { + // 1. Unassigned < assigned + // If only one of the pods is unassigned, the unassigned one is smaller + if s[i].Spec.NodeName != s[j].Spec.NodeName && (len(s[i].Spec.NodeName) == 0 || len(s[j].Spec.NodeName) == 0) { + return len(s[i].Spec.NodeName) == 0 + } + // 2. PodPending < PodUnknown < PodRunning + m := map[corev1.PodPhase]int{corev1.PodPending: 0, corev1.PodUnknown: 1, corev1.PodRunning: 2} + if m[s[i].Status.Phase] != m[s[j].Status.Phase] { + return m[s[i].Status.Phase] < m[s[j].Status.Phase] + } + // 3. Not ready < ready + // If only one of the pods is not ready, the not ready one is smaller + if IsPodReady(s[i]) != IsPodReady(s[j]) { + return !IsPodReady(s[i]) + } + // 4. Deleting < Not deleting + if isPodDeleting(s[i]) != isPodDeleting(s[j]) { + return isPodDeleting(s[i]) + } + // 5. Older deletion timestamp < newer deletion timestamp + if isPodDeleting(s[i]) && isPodDeleting(s[j]) && !s[i].ObjectMeta.DeletionTimestamp.Equal(s[j].ObjectMeta.DeletionTimestamp) { + return s[i].ObjectMeta.DeletionTimestamp.Before(s[j].ObjectMeta.DeletionTimestamp) + } + // TODO: take availability into account when we push minReadySeconds information from deployment into pods, + // see https://github.com/kubernetes/kubernetes/issues/22065 + // 6. Been ready for empty time < less time < more time + // If both pods are ready, the latest ready one is smaller + if IsPodReady(s[i]) && IsPodReady(s[j]) && !podReadyTime(s[i]).Equal(podReadyTime(s[j])) { + return afterOrZero(podReadyTime(s[i]), podReadyTime(s[j])) + } + // 7. Pods with containers with higher restart counts < lower restart counts + if maxContainerRestarts(s[i]) != maxContainerRestarts(s[j]) { + return maxContainerRestarts(s[i]) > maxContainerRestarts(s[j]) + } + // 8. Empty creation time pods < newer pods < older pods + if !s[i].CreationTimestamp.Equal(&s[j].CreationTimestamp) { + return afterOrZero(&s[i].CreationTimestamp, &s[j].CreationTimestamp) + } + return false +} + +// afterOrZero checks if time t1 is after time t2; if one of them +// is zero, the zero time is seen as after non-zero time. +func afterOrZero(t1, t2 *metav1.Time) bool { + if t1.Time.IsZero() || t2.Time.IsZero() { + return t1.Time.IsZero() + } + return t1.After(t2.Time) +} + +func podReadyTime(pod *corev1.Pod) *metav1.Time { + for _, c := range pod.Status.Conditions { + // we only care about pod ready conditions + if c.Type == corev1.PodReady && c.Status == corev1.ConditionTrue { + return &c.LastTransitionTime + } + } + return &metav1.Time{} +} + +func maxContainerRestarts(pod *corev1.Pod) int { + maxRestarts := 0 + for _, c := range pod.Status.ContainerStatuses { + maxRestarts = max(maxRestarts, int(c.RestartCount)) + } + return maxRestarts +} + +// ContainerType and VisitContainers are taken from +// https://github.com/kubernetes/kubernetes/blob/master/pkg/api/v1/pod/util.go +// kubectl cannot directly import this due to project goals + +// ContainerType signifies container type +type ContainerType int + +const ( + // Containers is for normal containers + Containers ContainerType = 1 << iota + // InitContainers is for init containers + InitContainers + // EphemeralContainers is for ephemeral containers + EphemeralContainers +) + +// AllContainers specifies that all containers be visited. +const AllContainers ContainerType = (InitContainers | Containers | EphemeralContainers) + +// ContainerVisitor is called with each container spec, and returns true +// if visiting should continue. +type ContainerVisitor func(container *corev1.Container, containerType ContainerType) (shouldContinue bool) + +// VisitContainers invokes the visitor function with a pointer to every container +// spec in the given pod spec with type set in mask. If visitor returns false, +// visiting is short-circuited. VisitContainers returns true if visiting completes, +// false if visiting was short-circuited. +func VisitContainers(podSpec *corev1.PodSpec, mask ContainerType, visitor ContainerVisitor) bool { + if mask&InitContainers != 0 { + for i := range podSpec.InitContainers { + if !visitor(&podSpec.InitContainers[i], InitContainers) { + return false + } + } + } + if mask&Containers != 0 { + for i := range podSpec.Containers { + if !visitor(&podSpec.Containers[i], Containers) { + return false + } + } + } + if mask&EphemeralContainers != 0 { + for i := range podSpec.EphemeralContainers { + if !visitor((*corev1.Container)(&podSpec.EphemeralContainers[i].EphemeralContainerCommon), EphemeralContainers) { + return false + } + } + } + return true +} diff --git a/vendor/k8s.io/kubectl/pkg/util/service_port.go b/vendor/k8s.io/kubectl/pkg/util/service_port.go new file mode 100644 index 0000000000..bc56ab7d6a --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/util/service_port.go @@ -0,0 +1,59 @@ +/* +Copyright 2018 The Kubernetes 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 util + +import ( + "fmt" + + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/intstr" +) + +// LookupContainerPortNumberByServicePort implements +// the handling of resolving container named port, as well as ignoring targetPort when clusterIP=None +// It returns an error when a named port can't find a match (with -1 returned), or when the service does not +// declare such port (with the input port number returned). +func LookupContainerPortNumberByServicePort(svc v1.Service, pod v1.Pod, port int32) (int32, error) { + for _, svcportspec := range svc.Spec.Ports { + if svcportspec.Port != port { + continue + } + if svc.Spec.ClusterIP == v1.ClusterIPNone { + return port, nil + } + if svcportspec.TargetPort.Type == intstr.Int { + if svcportspec.TargetPort.IntValue() == 0 { + // targetPort is omitted, and the IntValue() would be zero + return svcportspec.Port, nil + } + return int32(svcportspec.TargetPort.IntValue()), nil + } + return LookupContainerPortNumberByName(pod, svcportspec.TargetPort.String()) + } + return port, fmt.Errorf("Service %s does not have a service port %d", svc.Name, port) +} + +// LookupServicePortNumberByName find service port number by its named port name +func LookupServicePortNumberByName(svc v1.Service, name string) (int32, error) { + for _, svcportspec := range svc.Spec.Ports { + if svcportspec.Name == name { + return svcportspec.Port, nil + } + } + + return int32(-1), fmt.Errorf("Service '%s' does not have a named port '%s'", svc.Name, name) +} diff --git a/vendor/k8s.io/kubectl/pkg/util/umask.go b/vendor/k8s.io/kubectl/pkg/util/umask.go new file mode 100644 index 0000000000..3f0c4e83e6 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/util/umask.go @@ -0,0 +1,29 @@ +//go:build !windows +// +build !windows + +/* +Copyright 2014 The Kubernetes 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 util + +import ( + "golang.org/x/sys/unix" +) + +// Umask is a wrapper for `unix.Umask()` on non-Windows platforms +func Umask(mask int) (old int, err error) { + return unix.Umask(mask), nil +} diff --git a/vendor/k8s.io/kubectl/pkg/util/umask_windows.go b/vendor/k8s.io/kubectl/pkg/util/umask_windows.go new file mode 100644 index 0000000000..67f6efb974 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/util/umask_windows.go @@ -0,0 +1,29 @@ +//go:build windows +// +build windows + +/* +Copyright 2014 The Kubernetes 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 util + +import ( + "errors" +) + +// Umask returns an error on Windows +func Umask(mask int) (int, error) { + return 0, errors.New("platform and architecture is not supported") +} diff --git a/vendor/k8s.io/kubectl/pkg/util/util.go b/vendor/k8s.io/kubectl/pkg/util/util.go new file mode 100644 index 0000000000..ea57d3b39b --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/util/util.go @@ -0,0 +1,93 @@ +/* +Copyright 2017 The Kubernetes 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 util + +import ( + "crypto/md5" + "errors" + "fmt" + "path" + "path/filepath" + "strings" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// ParseRFC3339 parses an RFC3339 date in either RFC3339Nano or RFC3339 format. +func ParseRFC3339(s string, nowFn func() metav1.Time) (metav1.Time, error) { + if t, timeErr := time.Parse(time.RFC3339Nano, s); timeErr == nil { + return metav1.Time{Time: t}, nil + } + t, err := time.Parse(time.RFC3339, s) + if err != nil { + return metav1.Time{}, err + } + return metav1.Time{Time: t}, nil +} + +// HashObject returns the hash of a Object hash by a Codec +func HashObject(obj runtime.Object, codec runtime.Codec) (string, error) { + data, err := runtime.Encode(codec, obj) + if err != nil { + return "", err + } + return fmt.Sprintf("%x", md5.Sum(data)), nil +} + +// ParseFileSource parses the source given. +// +// Acceptable formats include: +// 1. source-path: the basename will become the key name +// 2. source-name=source-path: the source-name will become the key name and +// source-path is the path to the key file. +// +// Key names cannot include '='. +func ParseFileSource(source string) (keyName, filePath string, err error) { + numSeparators := strings.Count(source, "=") + switch { + case numSeparators == 0: + return path.Base(filepath.ToSlash(source)), source, nil + case numSeparators == 1 && strings.HasPrefix(source, "="): + return "", "", fmt.Errorf("key name for file path %v missing", strings.TrimPrefix(source, "=")) + case numSeparators == 1 && strings.HasSuffix(source, "="): + return "", "", fmt.Errorf("file path for key name %v missing", strings.TrimSuffix(source, "=")) + case numSeparators > 1: + return "", "", errors.New("key names or file paths cannot contain '='") + default: + components := strings.Split(source, "=") + return components[0], components[1], nil + } +} + +// ParseLiteralSource parses the source key=val pair into its component pieces. +// This functionality is distinguished from strings.SplitN(source, "=", 2) since +// it returns an error in the case of empty keys, values, or a missing equals sign. +func ParseLiteralSource(source string) (keyName, value string, err error) { + // leading equal is invalid + if strings.Index(source, "=") == 0 { + return "", "", fmt.Errorf("invalid literal source %v, expected key=value", source) + } + // split after the first equal (so values can have the = character) + items := strings.SplitN(source, "=", 2) + if len(items) != 2 { + return "", "", fmt.Errorf("invalid literal source %v, expected key=value", source) + } + + return items[0], items[1], nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 3d060f9e09..5919459fef 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -55,10 +55,10 @@ github.com/chai2010/gettext-go github.com/chai2010/gettext-go/mo github.com/chai2010/gettext-go/plural github.com/chai2010/gettext-go/po -# github.com/cilium/charts v0.0.0-20240926142256-e20f2b5f5344 +# github.com/cilium/charts v0.0.0-20241015090923-1f4c1b5ac12a ## explicit; go 1.17 github.com/cilium/charts -# github.com/cilium/cilium v1.17.0-pre.1 +# github.com/cilium/cilium v1.17.0-pre.2 ## explicit; go 1.23.0 github.com/cilium/cilium/api/v1/client github.com/cilium/cilium/api/v1/client/bgp @@ -135,11 +135,13 @@ github.com/cilium/cilium/pkg/command/exec github.com/cilium/cilium/pkg/common github.com/cilium/cilium/pkg/comparator github.com/cilium/cilium/pkg/container/bitlpm +github.com/cilium/cilium/pkg/container/set github.com/cilium/cilium/pkg/container/versioned github.com/cilium/cilium/pkg/controller github.com/cilium/cilium/pkg/crypto/certificatemanager github.com/cilium/cilium/pkg/datapath/linux/config/defines github.com/cilium/cilium/pkg/datapath/linux/probes +github.com/cilium/cilium/pkg/datapath/linux/safenetlink github.com/cilium/cilium/pkg/datapath/loader/metrics github.com/cilium/cilium/pkg/datapath/tables github.com/cilium/cilium/pkg/datapath/tunnel @@ -160,13 +162,13 @@ github.com/cilium/cilium/pkg/health/defaults github.com/cilium/cilium/pkg/hive github.com/cilium/cilium/pkg/hive/health github.com/cilium/cilium/pkg/hive/health/types +github.com/cilium/cilium/pkg/hubble github.com/cilium/cilium/pkg/iana github.com/cilium/cilium/pkg/identity github.com/cilium/cilium/pkg/identity/identitymanager github.com/cilium/cilium/pkg/identity/key github.com/cilium/cilium/pkg/identity/model github.com/cilium/cilium/pkg/idpool -github.com/cilium/cilium/pkg/inctimer github.com/cilium/cilium/pkg/ip github.com/cilium/cilium/pkg/ipam/option github.com/cilium/cilium/pkg/ipam/types @@ -245,6 +247,7 @@ github.com/cilium/cilium/pkg/policy/types github.com/cilium/cilium/pkg/promise github.com/cilium/cilium/pkg/rate github.com/cilium/cilium/pkg/rate/metrics +github.com/cilium/cilium/pkg/resiliency github.com/cilium/cilium/pkg/safeio github.com/cilium/cilium/pkg/safetime github.com/cilium/cilium/pkg/service/store @@ -271,12 +274,14 @@ github.com/cilium/ebpf/internal/sysenc github.com/cilium/ebpf/internal/tracefs github.com/cilium/ebpf/internal/unix github.com/cilium/ebpf/link -# github.com/cilium/hive v0.0.0-20240926131619-aa37668760f2 +# github.com/cilium/hive v0.0.0-20241021113747-bb8f3c0bede4 ## explicit; go 1.21.3 github.com/cilium/hive github.com/cilium/hive/cell github.com/cilium/hive/internal github.com/cilium/hive/job +github.com/cilium/hive/script +github.com/cilium/hive/script/internal/diff # github.com/cilium/proxy v0.0.0-20240909042906-ae435a5bef38 ## explicit; go 1.22 github.com/cilium/proxy/go/cilium/api @@ -527,7 +532,7 @@ github.com/cilium/proxy/go/envoy/type/tracing/v3 github.com/cilium/proxy/go/envoy/type/v3 github.com/cilium/proxy/go/envoy/watchdog/v3 github.com/cilium/proxy/pkg/policy/api/kafka -# github.com/cilium/statedb v0.3.0 +# github.com/cilium/statedb v0.3.2 ## explicit; go 1.23 github.com/cilium/statedb github.com/cilium/statedb/index @@ -658,7 +663,7 @@ github.com/evanphx/json-patch # github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d ## explicit github.com/exponent-io/jsonpath -# github.com/fatih/color v1.17.0 +# github.com/fatih/color v1.18.0 ## explicit; go 1.17 github.com/fatih/color # github.com/felixge/httpsnoop v1.0.4 @@ -975,7 +980,7 @@ github.com/pkg/browser # github.com/pkg/errors v0.9.1 ## explicit github.com/pkg/errors -# github.com/prometheus/client_golang v1.20.4 +# github.com/prometheus/client_golang v1.20.5 ## explicit; go 1.20 github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil/header @@ -986,8 +991,8 @@ github.com/prometheus/client_golang/prometheus/promhttp # github.com/prometheus/client_model v0.6.1 ## explicit; go 1.19 github.com/prometheus/client_model/go -# github.com/prometheus/common v0.59.1 -## explicit; go 1.20 +# github.com/prometheus/common v0.60.0 +## explicit; go 1.21 github.com/prometheus/common/expfmt github.com/prometheus/common/model # github.com/prometheus/procfs v0.15.1 @@ -1056,7 +1061,7 @@ github.com/spf13/viper/internal/features # github.com/subosito/gotenv v1.6.0 ## explicit; go 1.18 github.com/subosito/gotenv -# github.com/vishvananda/netlink v1.3.0 +# github.com/vishvananda/netlink v1.3.1-0.20241022031324-976bd8de7d81 ## explicit; go 1.12 github.com/vishvananda/netlink github.com/vishvananda/netlink/nl @@ -1141,7 +1146,7 @@ go.mongodb.org/mongo-driver/x/bsonx/bsoncore go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil -# go.opentelemetry.io/otel v1.30.0 +# go.opentelemetry.io/otel v1.31.0 ## explicit; go 1.22 go.opentelemetry.io/otel go.opentelemetry.io/otel/attribute @@ -1158,11 +1163,11 @@ go.opentelemetry.io/otel/semconv/v1.17.0/httpconv go.opentelemetry.io/otel/semconv/v1.20.0 go.opentelemetry.io/otel/semconv/v1.21.0 go.opentelemetry.io/otel/semconv/v1.24.0 -# go.opentelemetry.io/otel/metric v1.30.0 +# go.opentelemetry.io/otel/metric v1.31.0 ## explicit; go 1.22 go.opentelemetry.io/otel/metric go.opentelemetry.io/otel/metric/embedded -# go.opentelemetry.io/otel/trace v1.30.0 +# go.opentelemetry.io/otel/trace v1.31.0 ## explicit; go 1.22 go.opentelemetry.io/otel/trace go.opentelemetry.io/otel/trace/embedded @@ -1202,7 +1207,7 @@ go.uber.org/zap/zapgrpc # go4.org/netipx v0.0.0-20231129151722-fdeea329fbba ## explicit; go 1.18 go4.org/netipx -# golang.org/x/crypto v0.27.0 +# golang.org/x/crypto v0.28.0 ## explicit; go 1.20 golang.org/x/crypto/bcrypt golang.org/x/crypto/blowfish @@ -1229,7 +1234,7 @@ golang.org/x/exp/slices golang.org/x/exp/slog golang.org/x/exp/slog/internal golang.org/x/exp/slog/internal/buffer -# golang.org/x/net v0.29.0 +# golang.org/x/net v0.30.0 ## explicit; go 1.18 golang.org/x/net/context/ctxhttp golang.org/x/net/html @@ -1243,7 +1248,7 @@ golang.org/x/net/internal/timeseries golang.org/x/net/proxy golang.org/x/net/trace golang.org/x/net/websocket -# golang.org/x/oauth2 v0.22.0 +# golang.org/x/oauth2 v0.23.0 ## explicit; go 1.18 golang.org/x/oauth2 golang.org/x/oauth2/internal @@ -1251,16 +1256,16 @@ golang.org/x/oauth2/internal ## explicit; go 1.18 golang.org/x/sync/errgroup golang.org/x/sync/semaphore -# golang.org/x/sys v0.25.0 +# golang.org/x/sys v0.26.0 ## explicit; go 1.18 golang.org/x/sys/execabs golang.org/x/sys/plan9 golang.org/x/sys/unix golang.org/x/sys/windows -# golang.org/x/term v0.24.0 +# golang.org/x/term v0.25.0 ## explicit; go 1.18 golang.org/x/term -# golang.org/x/text v0.18.0 +# golang.org/x/text v0.19.0 ## explicit; go 1.18 golang.org/x/text/encoding golang.org/x/text/encoding/internal @@ -1272,18 +1277,21 @@ golang.org/x/text/secure/bidirule golang.org/x/text/transform golang.org/x/text/unicode/bidi golang.org/x/text/unicode/norm -# golang.org/x/time v0.6.0 +# golang.org/x/time v0.7.0 ## explicit; go 1.18 golang.org/x/time/rate +# golang.org/x/tools v0.26.0 +## explicit; go 1.22.0 +golang.org/x/tools/txtar # google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 ## explicit; go 1.21 google.golang.org/genproto/googleapis/api google.golang.org/genproto/googleapis/api/annotations google.golang.org/genproto/googleapis/api/expr/v1alpha1 -# google.golang.org/genproto/googleapis/rpc v0.0.0-20240924160255-9d4c2d233b61 +# google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 ## explicit; go 1.21 google.golang.org/genproto/googleapis/rpc/status -# google.golang.org/grpc v1.67.0 +# google.golang.org/grpc v1.67.1 ## explicit; go 1.21 google.golang.org/grpc google.golang.org/grpc/attributes @@ -1341,8 +1349,8 @@ google.golang.org/grpc/serviceconfig google.golang.org/grpc/stats google.golang.org/grpc/status google.golang.org/grpc/tap -# google.golang.org/protobuf v1.34.2 -## explicit; go 1.20 +# google.golang.org/protobuf v1.35.1 +## explicit; go 1.21 google.golang.org/protobuf/encoding/protodelim google.golang.org/protobuf/encoding/protojson google.golang.org/protobuf/encoding/prototext @@ -1399,7 +1407,7 @@ gopkg.in/yaml.v2 # gopkg.in/yaml.v3 v3.0.1 ## explicit gopkg.in/yaml.v3 -# helm.sh/helm/v3 v3.16.1 +# helm.sh/helm/v3 v3.16.2 ## explicit; go 1.22.0 helm.sh/helm/v3/internal/fileutil helm.sh/helm/v3/internal/resolver @@ -1441,7 +1449,7 @@ helm.sh/helm/v3/pkg/strvals helm.sh/helm/v3/pkg/time helm.sh/helm/v3/pkg/time/ctime helm.sh/helm/v3/pkg/uploader -# k8s.io/api v0.31.1 +# k8s.io/api v0.31.2 ## explicit; go 1.22.0 k8s.io/api/admission/v1 k8s.io/api/admission/v1beta1 @@ -1501,7 +1509,7 @@ k8s.io/api/storage/v1 k8s.io/api/storage/v1alpha1 k8s.io/api/storage/v1beta1 k8s.io/api/storagemigration/v1alpha1 -# k8s.io/apiextensions-apiserver v0.31.1 +# k8s.io/apiextensions-apiserver v0.31.2 ## explicit; go 1.22.0 k8s.io/apiextensions-apiserver/pkg/apis/apiextensions k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1 @@ -1517,7 +1525,7 @@ k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextension k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1/fake k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1 k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1/fake -# k8s.io/apimachinery v0.31.1 +# k8s.io/apimachinery v0.31.2 ## explicit; go 1.22.0 k8s.io/apimachinery/pkg/api/equality k8s.io/apimachinery/pkg/api/errors @@ -1581,16 +1589,16 @@ k8s.io/apimachinery/pkg/watch k8s.io/apimachinery/third_party/forked/golang/json k8s.io/apimachinery/third_party/forked/golang/netutil k8s.io/apimachinery/third_party/forked/golang/reflect -# k8s.io/apiserver v0.31.1 +# k8s.io/apiserver v0.31.2 ## explicit; go 1.22.0 k8s.io/apiserver/pkg/endpoints/deprecation -# k8s.io/cli-runtime v0.31.1 +# k8s.io/cli-runtime v0.31.2 ## explicit; go 1.22.0 k8s.io/cli-runtime/pkg/genericclioptions k8s.io/cli-runtime/pkg/genericiooptions k8s.io/cli-runtime/pkg/printers k8s.io/cli-runtime/pkg/resource -# k8s.io/client-go v0.31.1 +# k8s.io/client-go v0.31.2 ## explicit; go 1.22.0 k8s.io/client-go/applyconfigurations k8s.io/client-go/applyconfigurations/admissionregistration/v1 @@ -1813,7 +1821,7 @@ k8s.io/client-go/util/jsonpath k8s.io/client-go/util/keyutil k8s.io/client-go/util/watchlist k8s.io/client-go/util/workqueue -# k8s.io/component-base v0.31.1 +# k8s.io/component-base v0.31.2 ## explicit; go 1.22.0 k8s.io/component-base/version # k8s.io/klog/v2 v2.130.1 @@ -1837,13 +1845,15 @@ k8s.io/kube-openapi/pkg/spec3 k8s.io/kube-openapi/pkg/util/proto k8s.io/kube-openapi/pkg/util/proto/validation k8s.io/kube-openapi/pkg/validation/spec -# k8s.io/kubectl v0.31.0 +# k8s.io/kubectl v0.31.1 ## explicit; go 1.22.0 k8s.io/kubectl/pkg/cmd/util k8s.io/kubectl/pkg/scheme +k8s.io/kubectl/pkg/util k8s.io/kubectl/pkg/util/i18n k8s.io/kubectl/pkg/util/interrupt k8s.io/kubectl/pkg/util/openapi +k8s.io/kubectl/pkg/util/podutils k8s.io/kubectl/pkg/util/templates k8s.io/kubectl/pkg/util/term k8s.io/kubectl/pkg/validation @@ -1876,7 +1886,7 @@ oras.land/oras-go/pkg/target # sigs.k8s.io/controller-runtime v0.19.0 ## explicit; go 1.22.0 sigs.k8s.io/controller-runtime/pkg/client/apiutil -# sigs.k8s.io/gateway-api v1.2.0-rc1.0.20240923191000-5c5fc388829d +# sigs.k8s.io/gateway-api v1.2.0 ## explicit; go 1.22.0 sigs.k8s.io/gateway-api/apis/v1 sigs.k8s.io/gateway-api/apis/v1alpha2 @@ -1967,7 +1977,7 @@ sigs.k8s.io/kustomize/kyaml/yaml/merge2 sigs.k8s.io/kustomize/kyaml/yaml/merge3 sigs.k8s.io/kustomize/kyaml/yaml/schema sigs.k8s.io/kustomize/kyaml/yaml/walk -# sigs.k8s.io/mcs-api v0.1.1-0.20240919125245-7bbb5990134a +# sigs.k8s.io/mcs-api v0.1.1-0.20241002142749-eff1ba8c3ab2 ## explicit; go 1.22.0 sigs.k8s.io/mcs-api/pkg/apis/v1alpha1 sigs.k8s.io/mcs-api/pkg/client/clientset/versioned diff --git a/vendor/sigs.k8s.io/gateway-api/apis/v1alpha2/grpcroute_types.go b/vendor/sigs.k8s.io/gateway-api/apis/v1alpha2/grpcroute_types.go index 0b0bb4e85a..0750c2cb6b 100644 --- a/vendor/sigs.k8s.io/gateway-api/apis/v1alpha2/grpcroute_types.go +++ b/vendor/sigs.k8s.io/gateway-api/apis/v1alpha2/grpcroute_types.go @@ -24,6 +24,7 @@ import ( // +genclient // +kubebuilder:object:root=true +// +kubebuilder:skipversion // +kubebuilder:deprecatedversion:warning="The v1alpha2 version of GRPCRoute has been deprecated and will be removed in a future release of the API. Please upgrade to v1." type GRPCRoute v1.GRPCRoute diff --git a/vendor/sigs.k8s.io/gateway-api/apis/v1alpha2/referencegrant_types.go b/vendor/sigs.k8s.io/gateway-api/apis/v1alpha2/referencegrant_types.go index d673062608..372022f77c 100644 --- a/vendor/sigs.k8s.io/gateway-api/apis/v1alpha2/referencegrant_types.go +++ b/vendor/sigs.k8s.io/gateway-api/apis/v1alpha2/referencegrant_types.go @@ -26,6 +26,7 @@ import ( // +kubebuilder:object:root=true // +kubebuilder:resource:categories=gateway-api,shortName=refgrant // +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp` +// +kubebuilder:skipversion // +kubebuilder:deprecatedversion:warning="The v1alpha2 version of ReferenceGrant has been deprecated and will be removed in a future release of the API. Please upgrade to v1beta1." // ReferenceGrant identifies kinds of resources in other namespaces that are