package destination
import (
"context"
pb "github.com/linkerd/linkerd2-proxy-api/go/destination"
"github.com/linkerd/linkerd2/pkg/k8s"
"go.opencensus.io/plugin/ocgrpc"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
const (
destinationPort = 8086
destinationDeployment = "linkerd-destination"
)
// NewClient creates a client for the control plane Destination API that
// implements the Destination service.
func NewClient(addr string) (pb.DestinationClient, *grpc.ClientConn, error) {
conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithStatsHandler(&ocgrpc.ClientHandler{}))
if err != nil {
return nil, nil, err
}
return pb.NewDestinationClient(conn), conn, nil
}
// NewExternalClient creates a client for the control plane Destination API
// to run from outside a Kubernetes cluster.
func NewExternalClient(ctx context.Context, controlPlaneNamespace string, kubeAPI *k8s.KubernetesAPI, pod string) (pb.DestinationClient, *grpc.ClientConn, error) {
var portForward *k8s.PortForward
var err error
if pod == "" {
portForward, err = k8s.NewPortForward(
ctx,
kubeAPI,
controlPlaneNamespace,
destinationDeployment,
"localhost",
0,
destinationPort,
false,
)
} else {
portForward, err = k8s.NewPodPortForward(kubeAPI, controlPlaneNamespace, pod, "localhost", 0, destinationPort, false)
}
if err != nil {
return nil, nil, err
}
destinationAddress := portForward.AddressAndPort()
if err = portForward.Init(); err != nil {
return nil, nil, err
}
return NewClient(destinationAddress)
}
package destination
import (
"github.com/linkerd/linkerd2/controller/api/destination/watcher"
sp "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
log "github.com/sirupsen/logrus"
)
type dedupProfileListener struct {
parent watcher.ProfileUpdateListener
state *sp.ServiceProfile
initialized bool
log *log.Entry
}
func newDedupProfileListener(
parent watcher.ProfileUpdateListener,
log *log.Entry,
) watcher.ProfileUpdateListener {
return &dedupProfileListener{parent, nil, false, log}
}
func (p *dedupProfileListener) Update(profile *sp.ServiceProfile) {
if p.initialized && profile == p.state {
log.Debug("Skipping redundant update")
return
}
p.parent.Update(profile)
p.initialized = true
p.state = profile
}
package destination
import (
"github.com/linkerd/linkerd2/controller/api/destination/watcher"
sp "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
log "github.com/sirupsen/logrus"
)
type defaultProfileListener struct {
parent watcher.ProfileUpdateListener
profile *sp.ServiceProfile
log *log.Entry
}
func newDefaultProfileListener(
profile *sp.ServiceProfile,
parent watcher.ProfileUpdateListener,
log *log.Entry,
) watcher.ProfileUpdateListener {
return &defaultProfileListener{parent, profile, log}
}
func (p *defaultProfileListener) Update(profile *sp.ServiceProfile) {
if profile == nil {
log.Debug("Using default profile")
profile = p.profile
}
p.parent.Update(profile)
}
package destination
import (
"testing"
fuzz "github.com/AdaLogics/go-fuzz-headers"
pb "github.com/linkerd/linkerd2-proxy-api/go/destination"
"github.com/linkerd/linkerd2/controller/api/destination/watcher"
"github.com/linkerd/linkerd2/controller/api/util"
sp "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
logging "github.com/sirupsen/logrus"
)
func init() {
testing.Init()
}
// FuzzAdd fuzzes the Add method of the destination server.
func FuzzAdd(data []byte) int {
f := fuzz.NewConsumer(data)
set := watcher.AddressSet{}
err := f.GenerateStruct(&set)
if err != nil {
return 0
}
t := &testing.T{}
_, translator := makeEndpointTranslator(t)
translator.Start()
defer translator.Stop()
translator.Add(set)
translator.Remove(set)
return 1
}
// FuzzGet fuzzes the Get method of the destination server.
func FuzzGet(data []byte) int {
f := fuzz.NewConsumer(data)
dest1 := &pb.GetDestination{}
err := f.GenerateStruct(dest1)
if err != nil {
return 0
}
dest2 := &pb.GetDestination{}
err = f.GenerateStruct(dest2)
if err != nil {
return 0
}
dest3 := &pb.GetDestination{}
err = f.GenerateStruct(dest3)
if err != nil {
return 0
}
t := &testing.T{}
server := makeServer(t)
stream := &bufferingGetStream{
updates: make(chan *pb.Update, 50),
MockServerStream: util.NewMockServerStream(),
}
_ = server.Get(dest1, stream)
_ = server.Get(dest2, stream)
_ = server.Get(dest3, stream)
return 1
}
// FuzzGetProfile fuzzes the GetProfile method of the destination server.
func FuzzGetProfile(data []byte) int {
f := fuzz.NewConsumer(data)
dest := &pb.GetDestination{}
err := f.GenerateStruct(dest)
if err != nil {
return 0
}
t := &testing.T{}
server := makeServer(t)
stream := &bufferingGetProfileStream{
updates: []*pb.DestinationProfile{},
MockServerStream: util.NewMockServerStream(),
}
stream.Cancel()
_ = server.GetProfile(dest, stream)
return 1
}
// FuzzProfileTranslatorUpdate fuzzes the Update method of the profile translator.
func FuzzProfileTranslatorUpdate(data []byte) int {
f := fuzz.NewConsumer(data)
profile := &sp.ServiceProfile{}
err := f.GenerateStruct(profile)
if err != nil {
return 0
}
t := &testing.T{}
mockGetProfileServer := &mockDestinationGetProfileServer{profilesReceived: make(chan *pb.DestinationProfile, 50)}
translator := newProfileTranslator(mockGetProfileServer, logging.WithField("test", t.Name()), "foo.bar.svc.cluster.local", 80, nil)
translator.Start()
defer translator.Stop()
translator.Update(profile)
return 1
}
package destination
import (
"fmt"
pb "github.com/linkerd/linkerd2-proxy-api/go/destination"
"github.com/linkerd/linkerd2/controller/api/destination/watcher"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
logging "github.com/sirupsen/logrus"
"google.golang.org/protobuf/proto"
)
type endpointProfileTranslator struct {
enableH2Upgrade bool
controllerNS string
identityTrustDomain string
defaultOpaquePorts map[uint32]struct{}
meshedHttp2ClientParams *pb.Http2ClientParams
stream pb.Destination_GetProfileServer
endStream chan struct{}
updates chan *watcher.Address
stop chan struct{}
current *pb.DestinationProfile
log *logging.Entry
}
// endpointProfileUpdatesQueueOverflowCounter is a prometheus counter that is incremented
// whenever the profile updates queue overflows.
//
// We omit ip and port labels because they are high cardinality.
var endpointProfileUpdatesQueueOverflowCounter = promauto.NewCounter(
prometheus.CounterOpts{
Name: "endpoint_profile_updates_queue_overflow",
Help: "A counter incremented whenever the endpoint profile updates queue overflows",
},
)
// newEndpointProfileTranslator translates pod updates and profile updates to
// DestinationProfiles for endpoints
func newEndpointProfileTranslator(
enableH2Upgrade bool,
controllerNS,
identityTrustDomain string,
defaultOpaquePorts map[uint32]struct{},
meshedHTTP2ClientParams *pb.Http2ClientParams,
stream pb.Destination_GetProfileServer,
endStream chan struct{},
log *logging.Entry,
) *endpointProfileTranslator {
return &endpointProfileTranslator{
enableH2Upgrade: enableH2Upgrade,
controllerNS: controllerNS,
identityTrustDomain: identityTrustDomain,
defaultOpaquePorts: defaultOpaquePorts,
meshedHttp2ClientParams: meshedHTTP2ClientParams,
stream: stream,
endStream: endStream,
updates: make(chan *watcher.Address, updateQueueCapacity),
stop: make(chan struct{}),
log: log.WithField("component", "endpoint-profile-translator"),
}
}
// Start initiates a goroutine which processes update events off of the
// endpointProfileTranslator's internal queue and sends to the grpc stream as
// appropriate. The goroutine calls non-thread-safe Send, therefore Start must
// not be called more than once.
func (ept *endpointProfileTranslator) Start() {
go func() {
for {
select {
case update := <-ept.updates:
ept.update(update)
case <-ept.stop:
return
}
}
}()
}
// Stop terminates the goroutine started by Start.
func (ept *endpointProfileTranslator) Stop() {
close(ept.stop)
}
// Update enqueues an address update to be translated into a DestinationProfile.
// An error is returned if the update cannot be enqueued.
func (ept *endpointProfileTranslator) Update(address *watcher.Address) error {
select {
case ept.updates <- address:
// Update has been successfully enqueued.
return nil
default:
select {
case <-ept.endStream:
// The endStream channel has already been closed so no action is
// necessary.
return fmt.Errorf("profile update stream closed")
default:
// We are unable to enqueue because the channel does not have capacity.
// The stream has fallen too far behind and should be closed.
endpointProfileUpdatesQueueOverflowCounter.Inc()
close(ept.endStream)
return fmt.Errorf("profile update queue full; aborting stream")
}
}
}
func (ept *endpointProfileTranslator) queueLen() int {
return len(ept.updates)
}
func (ept *endpointProfileTranslator) update(address *watcher.Address) {
var opaquePorts map[uint32]struct{}
if address.Pod != nil {
opaquePorts = watcher.GetAnnotatedOpaquePorts(address.Pod, ept.defaultOpaquePorts)
} else {
opaquePorts = watcher.GetAnnotatedOpaquePortsForExternalWorkload(address.ExternalWorkload, ept.defaultOpaquePorts)
}
endpoint, err := ept.createEndpoint(*address, opaquePorts)
if err != nil {
ept.log.Errorf("Failed to create endpoint for %s:%d: %s",
address.IP, address.Port, err)
return
}
ept.log.Debugf("Created endpoint: %+v", endpoint)
_, opaqueProtocol := opaquePorts[address.Port]
profile := &pb.DestinationProfile{
RetryBudget: defaultRetryBudget(),
Endpoint: endpoint,
OpaqueProtocol: opaqueProtocol || address.OpaqueProtocol,
}
if proto.Equal(profile, ept.current) {
ept.log.Debugf("Ignoring redundant profile update: %+v", profile)
return
}
ept.log.Debugf("Sending profile update: %+v", profile)
if err := ept.stream.Send(profile); err != nil {
ept.log.Errorf("failed to send profile update: %s", err)
return
}
ept.current = profile
}
func (ept *endpointProfileTranslator) createEndpoint(address watcher.Address, opaquePorts map[uint32]struct{}) (*pb.WeightedAddr, error) {
var weightedAddr *pb.WeightedAddr
var err error
if address.ExternalWorkload != nil {
weightedAddr, err = createWeightedAddrForExternalWorkload(address, opaquePorts, ept.meshedHttp2ClientParams)
} else {
weightedAddr, err = createWeightedAddr(address, opaquePorts,
ept.enableH2Upgrade, ept.identityTrustDomain, ept.controllerNS, ept.meshedHttp2ClientParams)
}
if err != nil {
return nil, err
}
// `Get` doesn't include the namespace in the per-endpoint
// metadata, so it needs to be special-cased.
if address.Pod != nil {
weightedAddr.MetricLabels["namespace"] = address.Pod.Namespace
} else if address.ExternalWorkload != nil {
weightedAddr.MetricLabels["namespace"] = address.ExternalWorkload.Namespace
}
return weightedAddr, err
}
package destination
import (
"fmt"
"net/netip"
"reflect"
pb "github.com/linkerd/linkerd2-proxy-api/go/destination"
"github.com/linkerd/linkerd2-proxy-api/go/net"
"github.com/linkerd/linkerd2/controller/api/destination/watcher"
ewv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1"
"github.com/linkerd/linkerd2/controller/k8s"
"github.com/linkerd/linkerd2/pkg/addr"
pkgK8s "github.com/linkerd/linkerd2/pkg/k8s"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
logging "github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"
)
const (
defaultWeight uint32 = 10000
// inboundListenAddr is the environment variable holding the inbound
// listening address for the proxy container.
envInboundListenAddr = "LINKERD2_PROXY_INBOUND_LISTEN_ADDR"
updateQueueCapacity = 100
)
// endpointTranslator satisfies EndpointUpdateListener and translates updates
// into Destination.Get messages.
type (
endpointTranslator struct {
controllerNS string
identityTrustDomain string
nodeTopologyZone string
nodeName string
defaultOpaquePorts map[uint32]struct{}
enableH2Upgrade,
enableEndpointFiltering,
enableIPv6,
extEndpointZoneWeights bool
meshedHTTP2ClientParams *pb.Http2ClientParams
availableEndpoints watcher.AddressSet
filteredSnapshot watcher.AddressSet
stream pb.Destination_GetServer
endStream chan struct{}
log *logging.Entry
overflowCounter prometheus.Counter
updates chan interface{}
stop chan struct{}
}
addUpdate struct {
set watcher.AddressSet
}
removeUpdate struct {
set watcher.AddressSet
}
noEndpointsUpdate struct {
exists bool
}
)
var updatesQueueOverflowCounter = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "endpoint_updates_queue_overflow",
Help: "A counter incremented whenever the endpoint updates queue overflows",
},
[]string{
"service",
},
)
func newEndpointTranslator(
controllerNS string,
identityTrustDomain string,
enableH2Upgrade,
enableEndpointFiltering,
enableIPv6,
extEndpointZoneWeights bool,
meshedHTTP2ClientParams *pb.Http2ClientParams,
service string,
srcNodeName string,
defaultOpaquePorts map[uint32]struct{},
k8sAPI *k8s.MetadataAPI,
stream pb.Destination_GetServer,
endStream chan struct{},
log *logging.Entry,
) *endpointTranslator {
log = log.WithFields(logging.Fields{
"component": "endpoint-translator",
"service": service,
})
nodeTopologyZone, err := getNodeTopologyZone(k8sAPI, srcNodeName)
if err != nil {
log.Errorf("Failed to get node topology zone for node %s: %s", srcNodeName, err)
}
availableEndpoints := newEmptyAddressSet()
filteredSnapshot := newEmptyAddressSet()
return &endpointTranslator{
controllerNS,
identityTrustDomain,
nodeTopologyZone,
srcNodeName,
defaultOpaquePorts,
enableH2Upgrade,
enableEndpointFiltering,
enableIPv6,
extEndpointZoneWeights,
meshedHTTP2ClientParams,
availableEndpoints,
filteredSnapshot,
stream,
endStream,
log,
updatesQueueOverflowCounter.With(prometheus.Labels{"service": service}),
make(chan interface{}, updateQueueCapacity),
make(chan struct{}),
}
}
func (et *endpointTranslator) Add(set watcher.AddressSet) {
et.enqueueUpdate(&addUpdate{set})
}
func (et *endpointTranslator) Remove(set watcher.AddressSet) {
et.enqueueUpdate(&removeUpdate{set})
}
func (et *endpointTranslator) NoEndpoints(exists bool) {
et.enqueueUpdate(&noEndpointsUpdate{exists})
}
// Add, Remove, and NoEndpoints are called from a client-go informer callback
// and therefore must not block. For each of these, we enqueue an update in
// a channel so that it can be processed asyncronously. To ensure that enqueuing
// does not block, we first check to see if there is capacity in the buffered
// channel. If there is not, we drop the update and signal to the stream that
// it has fallen too far behind and should be closed.
func (et *endpointTranslator) enqueueUpdate(update interface{}) {
select {
case et.updates <- update:
// Update has been successfully enqueued.
default:
// We are unable to enqueue because the channel does not have capacity.
// The stream has fallen too far behind and should be closed.
et.overflowCounter.Inc()
select {
case <-et.endStream:
// The endStream channel has already been closed so no action is
// necessary.
default:
et.log.Error("endpoint update queue full; aborting stream")
close(et.endStream)
}
}
}
// Start initiates a goroutine which processes update events off of the
// endpointTranslator's internal queue and sends to the grpc stream as
// appropriate. The goroutine calls several non-thread-safe functions (including
// Send) and therefore, Start must not be called more than once.
func (et *endpointTranslator) Start() {
go func() {
for {
select {
case update := <-et.updates:
et.processUpdate(update)
case <-et.stop:
return
}
}
}()
}
// Stop terminates the goroutine started by Start.
func (et *endpointTranslator) Stop() {
close(et.stop)
}
func (et *endpointTranslator) processUpdate(update interface{}) {
switch update := update.(type) {
case *addUpdate:
et.add(update.set)
case *removeUpdate:
et.remove(update.set)
case *noEndpointsUpdate:
et.noEndpoints(update.exists)
}
}
func (et *endpointTranslator) add(set watcher.AddressSet) {
for id, address := range set.Addresses {
et.availableEndpoints.Addresses[id] = address
}
et.availableEndpoints.Labels = set.Labels
et.availableEndpoints.LocalTrafficPolicy = set.LocalTrafficPolicy
et.sendFilteredUpdate()
}
func (et *endpointTranslator) remove(set watcher.AddressSet) {
for id := range set.Addresses {
delete(et.availableEndpoints.Addresses, id)
}
et.sendFilteredUpdate()
}
func (et *endpointTranslator) noEndpoints(exists bool) {
et.log.Debugf("NoEndpoints(%+v)", exists)
et.availableEndpoints.Addresses = map[watcher.ID]watcher.Address{}
et.filteredSnapshot.Addresses = map[watcher.ID]watcher.Address{}
u := &pb.Update{
Update: &pb.Update_NoEndpoints{
NoEndpoints: &pb.NoEndpoints{
Exists: exists,
},
},
}
et.log.Debugf("Sending destination no endpoints: %+v", u)
if err := et.stream.Send(u); err != nil {
et.log.Debugf("Failed to send address update: %s", err)
}
}
func (et *endpointTranslator) sendFilteredUpdate() {
filtered := et.filterAddresses()
filtered = et.selectAddressFamily(filtered)
diffAdd, diffRemove := et.diffEndpoints(filtered)
if len(diffAdd.Addresses) > 0 {
et.sendClientAdd(diffAdd)
}
if len(diffRemove.Addresses) > 0 {
et.sendClientRemove(diffRemove)
}
et.filteredSnapshot = filtered
}
func (et *endpointTranslator) selectAddressFamily(addresses watcher.AddressSet) watcher.AddressSet {
filtered := make(map[watcher.ID]watcher.Address)
for id, addr := range addresses.Addresses {
if id.IPFamily == corev1.IPv6Protocol && !et.enableIPv6 {
continue
}
if id.IPFamily == corev1.IPv4Protocol && et.enableIPv6 {
// Only consider IPv4 address for which there's not already an IPv6
// alternative
altID := id
altID.IPFamily = corev1.IPv6Protocol
if _, ok := addresses.Addresses[altID]; ok {
continue
}
}
filtered[id] = addr
}
return watcher.AddressSet{
Addresses: filtered,
Labels: addresses.Labels,
LocalTrafficPolicy: addresses.LocalTrafficPolicy,
}
}
// filterAddresses is responsible for filtering endpoints based on the node's
// topology zone. The client will only receive endpoints with the same
// consumption zone as the node. An endpoints consumption zone is set
// by its Hints field and can be different than its actual Topology zone.
// when service.spec.internalTrafficPolicy is set to local, Topology Aware
// Hints are not used.
func (et *endpointTranslator) filterAddresses() watcher.AddressSet {
filtered := make(map[watcher.ID]watcher.Address)
// If endpoint filtering is disabled, return all available addresses.
if !et.enableEndpointFiltering {
for k, v := range et.availableEndpoints.Addresses {
filtered[k] = v
}
return watcher.AddressSet{
Addresses: filtered,
Labels: et.availableEndpoints.Labels,
}
}
// If service.spec.internalTrafficPolicy is set to local, filter and return the addresses
// for local node only
if et.availableEndpoints.LocalTrafficPolicy {
et.log.Debugf("Filtering through addresses that should be consumed by node %s", et.nodeName)
for id, address := range et.availableEndpoints.Addresses {
if address.Pod != nil && address.Pod.Spec.NodeName == et.nodeName {
filtered[id] = address
}
}
et.log.Debugf("Filtered from %d to %d addresses", len(et.availableEndpoints.Addresses), len(filtered))
return watcher.AddressSet{
Addresses: filtered,
Labels: et.availableEndpoints.Labels,
LocalTrafficPolicy: et.availableEndpoints.LocalTrafficPolicy,
}
}
// If any address does not have a hint, then all hints are ignored and all
// available addresses are returned. This replicates kube-proxy behavior
// documented in the KEP: https://github.com/kubernetes/enhancements/blob/master/keps/sig-network/2433-topology-aware-hints/README.md#kube-proxy
for _, address := range et.availableEndpoints.Addresses {
if len(address.ForZones) == 0 {
for k, v := range et.availableEndpoints.Addresses {
filtered[k] = v
}
et.log.Debugf("Hints not available on endpointslice. Zone Filtering disabled. Falling back to routing to all pods")
return watcher.AddressSet{
Addresses: filtered,
Labels: et.availableEndpoints.Labels,
LocalTrafficPolicy: et.availableEndpoints.LocalTrafficPolicy,
}
}
}
// Each address that has a hint matching the node's zone should be added
// to the set of addresses that will be returned.
et.log.Debugf("Filtering through addresses that should be consumed by zone %s", et.nodeTopologyZone)
for id, address := range et.availableEndpoints.Addresses {
for _, zone := range address.ForZones {
if zone.Name == et.nodeTopologyZone {
filtered[id] = address
}
}
}
if len(filtered) > 0 {
et.log.Debugf("Filtered from %d to %d addresses", len(et.availableEndpoints.Addresses), len(filtered))
return watcher.AddressSet{
Addresses: filtered,
Labels: et.availableEndpoints.Labels,
LocalTrafficPolicy: et.availableEndpoints.LocalTrafficPolicy,
}
}
// If there were no filtered addresses, then fall to using endpoints from
// all zones.
for k, v := range et.availableEndpoints.Addresses {
filtered[k] = v
}
return watcher.AddressSet{
Addresses: filtered,
Labels: et.availableEndpoints.Labels,
LocalTrafficPolicy: et.availableEndpoints.LocalTrafficPolicy,
}
}
// diffEndpoints calculates the difference between the filtered set of
// endpoints in the current (Add/Remove) operation and the snapshot of
// previously filtered endpoints. This diff allows the client to receive only
// the endpoints that match the topological zone, by adding new endpoints and
// removing stale ones.
func (et *endpointTranslator) diffEndpoints(filtered watcher.AddressSet) (watcher.AddressSet, watcher.AddressSet) {
add := make(map[watcher.ID]watcher.Address)
remove := make(map[watcher.ID]watcher.Address)
for id, new := range filtered.Addresses {
old, ok := et.filteredSnapshot.Addresses[id]
if !ok {
add[id] = new
} else if !reflect.DeepEqual(old, new) {
add[id] = new
}
}
for id, address := range et.filteredSnapshot.Addresses {
if _, ok := filtered.Addresses[id]; !ok {
remove[id] = address
}
}
return watcher.AddressSet{
Addresses: add,
Labels: filtered.Labels,
},
watcher.AddressSet{
Addresses: remove,
Labels: filtered.Labels,
}
}
func (et *endpointTranslator) sendClientAdd(set watcher.AddressSet) {
addrs := []*pb.WeightedAddr{}
for _, address := range set.Addresses {
var (
wa *pb.WeightedAddr
opaquePorts map[uint32]struct{}
err error
)
if address.Pod != nil {
opaquePorts = watcher.GetAnnotatedOpaquePorts(address.Pod, et.defaultOpaquePorts)
wa, err = createWeightedAddr(address, opaquePorts,
et.enableH2Upgrade, et.identityTrustDomain, et.controllerNS, et.meshedHTTP2ClientParams)
if err != nil {
et.log.Errorf("Failed to translate Pod endpoints to weighted addr: %s", err)
continue
}
} else if address.ExternalWorkload != nil {
opaquePorts = watcher.GetAnnotatedOpaquePortsForExternalWorkload(address.ExternalWorkload, et.defaultOpaquePorts)
wa, err = createWeightedAddrForExternalWorkload(address, opaquePorts, et.meshedHTTP2ClientParams)
if err != nil {
et.log.Errorf("Failed to translate ExternalWorkload endpoints to weighted addr: %s", err)
continue
}
} else {
// When there's no associated pod, we may still need to set metadata
// (especially for remote multi-cluster services).
var addr *net.TcpAddress
addr, err = toAddr(address)
if err != nil {
et.log.Errorf("Failed to translate endpoints to weighted addr: %s", err)
continue
}
var authOverride *pb.AuthorityOverride
if address.AuthorityOverride != "" {
authOverride = &pb.AuthorityOverride{
AuthorityOverride: address.AuthorityOverride,
}
}
wa = &pb.WeightedAddr{
Addr: addr,
Weight: defaultWeight,
AuthorityOverride: authOverride,
}
if address.Identity != "" {
wa.TlsIdentity = &pb.TlsIdentity{
Strategy: &pb.TlsIdentity_DnsLikeIdentity_{
DnsLikeIdentity: &pb.TlsIdentity_DnsLikeIdentity{
Name: address.Identity,
},
},
}
if et.enableH2Upgrade {
wa.ProtocolHint = &pb.ProtocolHint{
Protocol: &pb.ProtocolHint_H2_{
H2: &pb.ProtocolHint_H2{},
},
}
}
wa.Http2 = et.meshedHTTP2ClientParams
}
}
if et.extEndpointZoneWeights {
// EXPERIMENTAL: Use the endpoint weight field to indicate zonal
// preference so that local endoints are more heavily weighted.
if et.nodeTopologyZone != "" && address.Zone != nil && *address.Zone == et.nodeTopologyZone {
wa.Weight *= 10
}
}
addrs = append(addrs, wa)
}
add := &pb.Update{Update: &pb.Update_Add{
Add: &pb.WeightedAddrSet{
Addrs: addrs,
MetricLabels: set.Labels,
},
}}
et.log.Debugf("Sending destination add: %+v", add)
if err := et.stream.Send(add); err != nil {
et.log.Debugf("Failed to send address update: %s", err)
}
}
func (et *endpointTranslator) sendClientRemove(set watcher.AddressSet) {
addrs := []*net.TcpAddress{}
for _, address := range set.Addresses {
tcpAddr, err := toAddr(address)
if err != nil {
et.log.Errorf("Failed to translate endpoints to addr: %s", err)
continue
}
addrs = append(addrs, tcpAddr)
}
remove := &pb.Update{Update: &pb.Update_Remove{
Remove: &pb.AddrSet{
Addrs: addrs,
},
}}
et.log.Debugf("Sending destination remove: %+v", remove)
if err := et.stream.Send(remove); err != nil {
et.log.Debugf("Failed to send address update: %s", err)
}
}
func toAddr(address watcher.Address) (*net.TcpAddress, error) {
ip, err := addr.ParseProxyIP(address.IP)
if err != nil {
return nil, err
}
return &net.TcpAddress{
Ip: ip,
Port: address.Port,
}, nil
}
func createWeightedAddrForExternalWorkload(
address watcher.Address,
opaquePorts map[uint32]struct{},
http2 *pb.Http2ClientParams,
) (*pb.WeightedAddr, error) {
tcpAddr, err := toAddr(address)
if err != nil {
return nil, err
}
weightedAddr := pb.WeightedAddr{
Addr: tcpAddr,
Weight: defaultWeight,
MetricLabels: map[string]string{},
}
weightedAddr.MetricLabels = pkgK8s.GetExternalWorkloadLabels(address.OwnerKind, address.OwnerName, address.ExternalWorkload)
// If the address is not backed by an ExternalWorkload, there is no additional metadata
// to add.
if address.ExternalWorkload == nil {
return &weightedAddr, nil
}
weightedAddr.ProtocolHint = &pb.ProtocolHint{}
weightedAddr.Http2 = http2
_, opaquePort := opaquePorts[address.Port]
// If address is set as opaque by a Server, or its port is set as
// opaque by annotation or default value, then set the hinted protocol to
// Opaque.
if address.OpaqueProtocol || opaquePort {
weightedAddr.ProtocolHint.Protocol = &pb.ProtocolHint_Opaque_{
Opaque: &pb.ProtocolHint_Opaque{},
}
port, err := getInboundPortFromExternalWorkload(&address.ExternalWorkload.Spec)
if err != nil {
return nil, fmt.Errorf("failed to read inbound port: %w", err)
}
weightedAddr.ProtocolHint.OpaqueTransport = &pb.ProtocolHint_OpaqueTransport{
InboundPort: port,
}
} else {
weightedAddr.ProtocolHint.Protocol = &pb.ProtocolHint_H2_{
H2: &pb.ProtocolHint_H2{},
}
}
// we assume external workloads support only SPIRE identity
weightedAddr.TlsIdentity = &pb.TlsIdentity{
Strategy: &pb.TlsIdentity_UriLikeIdentity_{
UriLikeIdentity: &pb.TlsIdentity_UriLikeIdentity{
Uri: address.ExternalWorkload.Spec.MeshTLS.Identity,
},
},
ServerName: &pb.TlsIdentity_DnsLikeIdentity{
Name: address.ExternalWorkload.Spec.MeshTLS.ServerName,
},
}
weightedAddr.MetricLabels = pkgK8s.GetExternalWorkloadLabels(address.OwnerKind, address.OwnerName, address.ExternalWorkload)
// Set a zone label, even if it is empty (for consistency).
z := ""
if address.Zone != nil {
z = *address.Zone
}
weightedAddr.MetricLabels["zone"] = z
return &weightedAddr, nil
}
func createWeightedAddr(
address watcher.Address,
opaquePorts map[uint32]struct{},
enableH2Upgrade bool,
identityTrustDomain string,
controllerNS string,
meshedHttp2 *pb.Http2ClientParams,
) (*pb.WeightedAddr, error) {
tcpAddr, err := toAddr(address)
if err != nil {
return nil, err
}
weightedAddr := pb.WeightedAddr{
Addr: tcpAddr,
Weight: defaultWeight,
MetricLabels: map[string]string{},
}
// If the address is not backed by a pod, there is no additional metadata
// to add.
if address.Pod == nil {
return &weightedAddr, nil
}
skippedInboundPorts := getPodSkippedInboundPortsAnnotations(address.Pod)
controllerNSLabel := address.Pod.Labels[pkgK8s.ControllerNSLabel]
sa, ns := pkgK8s.GetServiceAccountAndNS(address.Pod)
weightedAddr.MetricLabels = pkgK8s.GetPodLabels(address.OwnerKind, address.OwnerName, address.Pod)
// Set a zone label, even if it is empty (for consistency).
z := ""
if address.Zone != nil {
z = *address.Zone
}
weightedAddr.MetricLabels["zone"] = z
_, isSkippedInboundPort := skippedInboundPorts[address.Port]
if controllerNSLabel != "" && !isSkippedInboundPort {
weightedAddr.Http2 = meshedHttp2
weightedAddr.ProtocolHint = &pb.ProtocolHint{}
_, opaquePort := opaquePorts[address.Port]
// If address is set as opaque by a Server, or its port is set as
// opaque by annotation or default value, then set the hinted protocol to
// Opaque.
if address.OpaqueProtocol || opaquePort {
weightedAddr.ProtocolHint.Protocol = &pb.ProtocolHint_Opaque_{
Opaque: &pb.ProtocolHint_Opaque{},
}
port, err := getInboundPort(&address.Pod.Spec)
if err != nil {
return nil, fmt.Errorf("failed to read inbound port: %w", err)
}
weightedAddr.ProtocolHint.OpaqueTransport = &pb.ProtocolHint_OpaqueTransport{
InboundPort: port,
}
} else if enableH2Upgrade {
// If the pod is controlled by any Linkerd control plane, then it can be
// hinted that this destination knows H2 (and handles our orig-proto
// translation)
weightedAddr.ProtocolHint.Protocol = &pb.ProtocolHint_H2_{
H2: &pb.ProtocolHint_H2{},
}
}
}
// If the pod is controlled by the same Linkerd control plane, then it can
// participate in identity with peers.
//
// TODO this should be relaxed to match a trust domain annotation so that
// multiple meshes can participate in identity if they share trust roots.
if identityTrustDomain != "" &&
controllerNSLabel == controllerNS &&
!isSkippedInboundPort {
id := fmt.Sprintf("%s.%s.serviceaccount.identity.%s.%s", sa, ns, controllerNSLabel, identityTrustDomain)
tlsId := &pb.TlsIdentity_DnsLikeIdentity{Name: id}
weightedAddr.TlsIdentity = &pb.TlsIdentity{
Strategy: &pb.TlsIdentity_DnsLikeIdentity_{
DnsLikeIdentity: tlsId,
},
ServerName: tlsId,
}
}
return &weightedAddr, nil
}
func getNodeTopologyZone(k8sAPI *k8s.MetadataAPI, srcNode string) (string, error) {
node, err := k8sAPI.Get(k8s.Node, srcNode)
if err != nil {
return "", err
}
if zone, ok := node.Labels[corev1.LabelTopologyZone]; ok {
return zone, nil
}
return "", nil
}
func newEmptyAddressSet() watcher.AddressSet {
return watcher.AddressSet{
Addresses: make(map[watcher.ID]watcher.Address),
Labels: make(map[string]string),
}
}
// getInboundPort gets the inbound port from the proxy container's environment
// variable.
func getInboundPort(podSpec *corev1.PodSpec) (uint32, error) {
containers := append(podSpec.InitContainers, podSpec.Containers...)
for _, containerSpec := range containers {
if containerSpec.Name != pkgK8s.ProxyContainerName {
continue
}
for _, envVar := range containerSpec.Env {
if envVar.Name != envInboundListenAddr {
continue
}
addrPort, err := netip.ParseAddrPort(envVar.Value)
if err != nil {
return 0, fmt.Errorf("failed to parse inbound port for proxy container: %w", err)
}
return uint32(addrPort.Port()), nil
}
}
return 0, fmt.Errorf("failed to find %s environment variable in any container for given pod spec", envInboundListenAddr)
}
// getInboundPortFromExternalWorkload gets the inbound port from the ExternalWorkload spec
// variable.
func getInboundPortFromExternalWorkload(ewSpec *ewv1beta1.ExternalWorkloadSpec) (uint32, error) {
for _, p := range ewSpec.Ports {
if p.Name == pkgK8s.ProxyPortName {
return uint32(p.Port), nil
}
}
return 0, fmt.Errorf("failed to find %s port for given ExternalWorkloadSpec", pkgK8s.ProxyPortName)
}
package destination
import (
"sync"
"github.com/linkerd/linkerd2/controller/api/destination/watcher"
sp "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
logging "github.com/sirupsen/logrus"
)
type fallbackProfileListener struct {
primary, backup *childListener
parent watcher.ProfileUpdateListener
log *logging.Entry
mutex sync.Mutex
}
type childListener struct {
// state is only referenced from the outer struct primaryProfileListener
// or backupProfileListener (e.g. listener.state where listener's type is
// _not_ this struct). structcheck issues a false positive for this field
// as it does not think it's used.
//nolint:structcheck
state *sp.ServiceProfile
initialized bool
parent *fallbackProfileListener
}
// newFallbackProfileListener takes a parent ProfileUpdateListener and returns
// two ProfileUpdateListeners: a primary and a backup.
//
// If the primary listener is updated with a non-nil value, it is published to
// the parent listener.
//
// Otherwise, if the backup listener has most recently been updated with a
// non-nil value, its valeu is published to the parent listener.
//
// A nil ServiceProfile is published only when both the primary and backup have
// been initialized and have nil values.
func newFallbackProfileListener(
parent watcher.ProfileUpdateListener,
log *logging.Entry,
) (watcher.ProfileUpdateListener, watcher.ProfileUpdateListener) {
// Primary and backup share a lock to ensure updates are atomic.
fallback := fallbackProfileListener{
mutex: sync.Mutex{},
log: log,
}
primary := childListener{
initialized: false,
parent: &fallback,
}
backup := childListener{
initialized: false,
parent: &fallback,
}
fallback.parent = parent
fallback.primary = &primary
fallback.backup = &backup
return &primary, &backup
}
func (f *fallbackProfileListener) publish() {
if !f.primary.initialized {
f.log.Debug("Waiting for primary profile listener to be initialized")
return
}
if !f.backup.initialized {
f.log.Debug("Waiting for backup profile listener to be initialized")
return
}
if f.primary.state == nil && f.backup.state != nil {
f.log.Debug("Publishing backup profile")
f.parent.Update(f.backup.state)
return
}
f.log.Debug("Publishing primary profile")
f.parent.Update(f.primary.state)
}
func (p *childListener) Update(profile *sp.ServiceProfile) {
p.parent.mutex.Lock()
defer p.parent.mutex.Unlock()
p.state = profile
p.initialized = true
p.parent.publish()
}
package destination
import (
"github.com/linkerd/linkerd2/controller/api/destination/watcher"
sp "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
)
// opaquePortsAdaptor holds an underlying ProfileUpdateListener and updates
// that listener with changes to a service's opaque ports annotation. It
// implements OpaquePortsUpdateListener and should be passed to a source of
// profile updates and opaque ports updates.
type opaquePortsAdaptor struct {
listener watcher.ProfileUpdateListener
profile *sp.ServiceProfile
opaquePorts map[uint32]struct{}
}
func newOpaquePortsAdaptor(listener watcher.ProfileUpdateListener) *opaquePortsAdaptor {
return &opaquePortsAdaptor{
listener: listener,
}
}
func (opa *opaquePortsAdaptor) Update(profile *sp.ServiceProfile) {
opa.profile = profile
opa.publish()
}
func (opa *opaquePortsAdaptor) UpdateService(ports map[uint32]struct{}) {
opa.opaquePorts = ports
opa.publish()
}
func (opa *opaquePortsAdaptor) publish() {
if opa.profile != nil {
p := *opa.profile
p.Spec.OpaquePorts = opa.opaquePorts
opa.listener.Update(&p)
}
}
package destination
import (
"errors"
"fmt"
"net"
"time"
"github.com/golang/protobuf/ptypes/duration"
pb "github.com/linkerd/linkerd2-proxy-api/go/destination"
sp "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
"github.com/linkerd/linkerd2/pkg/profiles"
"github.com/linkerd/linkerd2/pkg/util"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
logging "github.com/sirupsen/logrus"
)
const millisPerDecimilli = 10
// implements the ProfileUpdateListener interface
type profileTranslator struct {
fullyQualifiedName string
port uint32
stream pb.Destination_GetProfileServer
endStream chan struct{}
log *logging.Entry
overflowCounter prometheus.Counter
updates chan *sp.ServiceProfile
stop chan struct{}
}
var profileUpdatesQueueOverflowCounter = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "profile_updates_queue_overflow",
Help: "A counter incremented whenever the profile updates queue overflows",
},
[]string{
"fqn",
"port",
},
)
func newProfileTranslator(stream pb.Destination_GetProfileServer, log *logging.Entry, fqn string, port uint32, endStream chan struct{}) *profileTranslator {
return &profileTranslator{
fullyQualifiedName: fqn,
port: port,
stream: stream,
endStream: endStream,
log: log.WithField("component", "profile-translator"),
overflowCounter: profileUpdatesQueueOverflowCounter.With(prometheus.Labels{"fqn": fqn, "port": fmt.Sprintf("%d", port)}),
updates: make(chan *sp.ServiceProfile, updateQueueCapacity),
stop: make(chan struct{}),
}
}
// Update is called from a client-go informer callback and therefore must not
// We enqueue an update in a channel so that it can be processed asyncronously.
// To ensure that enqueuing does not block, we first check to see if there is
// capacity in the buffered channel. If there is not, we drop the update and
// signal to the stream that it has fallen too far behind and should be closed.
func (pt *profileTranslator) Update(profile *sp.ServiceProfile) {
select {
case pt.updates <- profile:
// Update has been successfully enqueued.
default:
// We are unable to enqueue because the channel does not have capacity.
// The stream has fallen too far behind and should be closed.
pt.overflowCounter.Inc()
select {
case <-pt.endStream:
// The endStream channel has already been closed so no action is
// necessary.
default:
pt.log.Error("profile update queue full; aborting stream")
close(pt.endStream)
}
}
}
// Start initiates a goroutine which processes update events off of the
// profileTranslator's internal queue and sends to the grpc stream as
// appropriate. The goroutine calls non-thread-safe Send, therefore Start must
// not be called more than once.
func (pt *profileTranslator) Start() {
go func() {
for {
select {
case update := <-pt.updates:
pt.update(update)
case <-pt.stop:
return
}
}
}()
}
// Stop terminates the goroutine started by Start.
func (pt *profileTranslator) Stop() {
close(pt.stop)
}
func (pt *profileTranslator) update(profile *sp.ServiceProfile) {
if profile == nil {
pt.log.Debugf("Sending default profile")
if err := pt.stream.Send(pt.defaultServiceProfile()); err != nil {
pt.log.Errorf("failed to send default service profile: %s", err)
}
return
}
destinationProfile, err := pt.createDestinationProfile(profile)
if err != nil {
pt.log.Error(err)
return
}
pt.log.Debugf("Sending profile update: %+v", destinationProfile)
if err := pt.stream.Send(destinationProfile); err != nil {
pt.log.Errorf("failed to send profile update: %s", err)
}
}
func (pt *profileTranslator) defaultServiceProfile() *pb.DestinationProfile {
return &pb.DestinationProfile{
Routes: []*pb.Route{},
RetryBudget: defaultRetryBudget(),
FullyQualifiedName: pt.fullyQualifiedName,
}
}
func defaultRetryBudget() *pb.RetryBudget {
return &pb.RetryBudget{
MinRetriesPerSecond: 10,
RetryRatio: 0.2,
Ttl: &duration.Duration{
Seconds: 10,
},
}
}
func toDuration(d time.Duration) *duration.Duration {
if d == 0 {
return nil
}
return &duration.Duration{
Seconds: int64(d / time.Second),
Nanos: int32(d % time.Second),
}
}
// createDestinationProfile returns a Proxy API DestinationProfile, given a
// ServiceProfile.
func (pt *profileTranslator) createDestinationProfile(profile *sp.ServiceProfile) (*pb.DestinationProfile, error) {
routes := make([]*pb.Route, 0)
for _, route := range profile.Spec.Routes {
pbRoute, err := toRoute(profile, route)
if err != nil {
return nil, err
}
routes = append(routes, pbRoute)
}
budget := defaultRetryBudget()
if profile.Spec.RetryBudget != nil {
budget.MinRetriesPerSecond = profile.Spec.RetryBudget.MinRetriesPerSecond
budget.RetryRatio = profile.Spec.RetryBudget.RetryRatio
ttl, err := time.ParseDuration(profile.Spec.RetryBudget.TTL)
if err != nil {
return nil, err
}
budget.Ttl = toDuration(ttl)
}
var opaqueProtocol bool
if profile.Spec.OpaquePorts != nil {
_, opaqueProtocol = profile.Spec.OpaquePorts[pt.port]
}
return &pb.DestinationProfile{
Routes: routes,
RetryBudget: budget,
DstOverrides: toDstOverrides(profile.Spec.DstOverrides, pt.port),
FullyQualifiedName: pt.fullyQualifiedName,
OpaqueProtocol: opaqueProtocol,
}, nil
}
func toDstOverrides(dsts []*sp.WeightedDst, port uint32) []*pb.WeightedDst {
pbDsts := []*pb.WeightedDst{}
for _, dst := range dsts {
authority := dst.Authority
// If the authority does not parse as a host:port, add the port provided by `GetProfile`.
if _, _, err := net.SplitHostPort(authority); err != nil {
authority = net.JoinHostPort(authority, fmt.Sprintf("%d", port))
}
pbDst := &pb.WeightedDst{
Authority: authority,
// Weights are expressed in decimillis: 10_000 represents 100%
Weight: uint32(dst.Weight.MilliValue() * millisPerDecimilli),
}
pbDsts = append(pbDsts, pbDst)
}
return pbDsts
}
// toRoute returns a Proxy API Route, given a ServiceProfile Route.
func toRoute(profile *sp.ServiceProfile, route *sp.RouteSpec) (*pb.Route, error) {
cond, err := toRequestMatch(route.Condition)
if err != nil {
return nil, err
}
rcs := make([]*pb.ResponseClass, 0)
for _, rc := range route.ResponseClasses {
pbRc, err := toResponseClass(rc)
if err != nil {
return nil, err
}
rcs = append(rcs, pbRc)
}
var timeout time.Duration // No default timeout
if route.Timeout != "" {
timeout, err = time.ParseDuration(route.Timeout)
if err != nil {
logging.Errorf(
"failed to parse duration for route '%s' in service profile '%s' in namespace '%s': %s",
route.Name,
profile.Name,
profile.Namespace,
err,
)
}
}
return &pb.Route{
Condition: cond,
ResponseClasses: rcs,
MetricsLabels: map[string]string{"route": route.Name},
IsRetryable: route.IsRetryable,
Timeout: toDuration(timeout),
}, nil
}
// toResponseClass returns a Proxy API ResponseClass, given a ServiceProfile
// ResponseClass.
func toResponseClass(rc *sp.ResponseClass) (*pb.ResponseClass, error) {
cond, err := toResponseMatch(rc.Condition)
if err != nil {
return nil, err
}
return &pb.ResponseClass{
Condition: cond,
IsFailure: rc.IsFailure,
}, nil
}
// toResponseMatch returns a Proxy API ResponseMatch, given a ServiceProfile
// ResponseMatch.
func toResponseMatch(rspMatch *sp.ResponseMatch) (*pb.ResponseMatch, error) {
if rspMatch == nil {
return nil, errors.New("missing response match")
}
err := profiles.ValidateResponseMatch(rspMatch)
if err != nil {
return nil, err
}
matches := make([]*pb.ResponseMatch, 0)
if rspMatch.All != nil {
all := make([]*pb.ResponseMatch, 0)
for _, m := range rspMatch.All {
pbM, err := toResponseMatch(m)
if err != nil {
return nil, err
}
all = append(all, pbM)
}
matches = append(matches, &pb.ResponseMatch{
Match: &pb.ResponseMatch_All{
All: &pb.ResponseMatch_Seq{
Matches: all,
},
},
})
}
if rspMatch.Any != nil {
any := make([]*pb.ResponseMatch, 0)
for _, m := range rspMatch.Any {
pbM, err := toResponseMatch(m)
if err != nil {
return nil, err
}
any = append(any, pbM)
}
matches = append(matches, &pb.ResponseMatch{
Match: &pb.ResponseMatch_Any{
Any: &pb.ResponseMatch_Seq{
Matches: any,
},
},
})
}
if rspMatch.Status != nil {
matches = append(matches, &pb.ResponseMatch{
Match: &pb.ResponseMatch_Status{
Status: &pb.HttpStatusRange{
Max: rspMatch.Status.Max,
Min: rspMatch.Status.Min,
},
},
})
}
if rspMatch.Not != nil {
not, err := toResponseMatch(rspMatch.Not)
if err != nil {
return nil, err
}
matches = append(matches, &pb.ResponseMatch{
Match: &pb.ResponseMatch_Not{
Not: not,
},
})
}
if len(matches) == 0 {
return nil, errors.New("a response match must have a field set")
}
if len(matches) == 1 {
return matches[0], nil
}
return &pb.ResponseMatch{
Match: &pb.ResponseMatch_All{
All: &pb.ResponseMatch_Seq{
Matches: matches,
},
},
}, nil
}
// toRequestMatch returns a Proxy API RequestMatch, given a ServiceProfile
// RequestMatch.
func toRequestMatch(reqMatch *sp.RequestMatch) (*pb.RequestMatch, error) {
if reqMatch == nil {
return nil, errors.New("missing request match")
}
err := profiles.ValidateRequestMatch(reqMatch)
if err != nil {
return nil, err
}
matches := make([]*pb.RequestMatch, 0)
if reqMatch.All != nil {
all := make([]*pb.RequestMatch, 0)
for _, m := range reqMatch.All {
pbM, err := toRequestMatch(m)
if err != nil {
return nil, err
}
all = append(all, pbM)
}
matches = append(matches, &pb.RequestMatch{
Match: &pb.RequestMatch_All{
All: &pb.RequestMatch_Seq{
Matches: all,
},
},
})
}
if reqMatch.Any != nil {
any := make([]*pb.RequestMatch, 0)
for _, m := range reqMatch.Any {
pbM, err := toRequestMatch(m)
if err != nil {
return nil, err
}
any = append(any, pbM)
}
matches = append(matches, &pb.RequestMatch{
Match: &pb.RequestMatch_Any{
Any: &pb.RequestMatch_Seq{
Matches: any,
},
},
})
}
if reqMatch.Method != "" {
matches = append(matches, &pb.RequestMatch{
Match: &pb.RequestMatch_Method{
Method: util.ParseMethod(reqMatch.Method),
},
})
}
if reqMatch.Not != nil {
not, err := toRequestMatch(reqMatch.Not)
if err != nil {
return nil, err
}
matches = append(matches, &pb.RequestMatch{
Match: &pb.RequestMatch_Not{
Not: not,
},
})
}
if reqMatch.PathRegex != "" {
matches = append(matches, &pb.RequestMatch{
Match: &pb.RequestMatch_Path{
Path: &pb.PathMatch{
Regex: reqMatch.PathRegex,
},
},
})
}
if len(matches) == 0 {
return nil, errors.New("a request match must have a field set")
}
if len(matches) == 1 {
return matches[0], nil
}
return &pb.RequestMatch{
Match: &pb.RequestMatch_All{
All: &pb.RequestMatch_Seq{
Matches: matches,
},
},
}, nil
}
package destination
import (
"encoding/json"
"errors"
"fmt"
"net"
"strconv"
"strings"
pb "github.com/linkerd/linkerd2-proxy-api/go/destination"
"github.com/linkerd/linkerd2/controller/api/destination/watcher"
sp "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
"github.com/linkerd/linkerd2/controller/k8s"
labels "github.com/linkerd/linkerd2/pkg/k8s"
"github.com/linkerd/linkerd2/pkg/prometheus"
"github.com/linkerd/linkerd2/pkg/util"
logging "github.com/sirupsen/logrus"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/peer"
"google.golang.org/grpc/status"
corev1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
)
type (
Config struct {
ControllerNS,
IdentityTrustDomain,
ClusterDomain string
EnableH2Upgrade,
EnableEndpointSlices,
EnableIPv6,
ExtEndpointZoneWeights bool
MeshedHttp2ClientParams *pb.Http2ClientParams
DefaultOpaquePorts map[uint32]struct{}
}
server struct {
pb.UnimplementedDestinationServer
config Config
workloads *watcher.WorkloadWatcher
endpoints *watcher.EndpointsWatcher
opaquePorts *watcher.OpaquePortsWatcher
profiles *watcher.ProfileWatcher
clusterStore *watcher.ClusterStore
k8sAPI *k8s.API
metadataAPI *k8s.MetadataAPI
log *logging.Entry
shutdown <-chan struct{}
}
)
// NewServer returns a new instance of the destination server.
//
// The destination server serves service discovery and other information to the
// proxy. This implementation supports the "k8s" destination scheme and expects
// destination paths to be of the form:
// <service>.<namespace>.svc.cluster.local:<port>
//
// If the port is omitted, 80 is used as a default. If the namespace is
// omitted, "default" is used as a default.append
//
// Addresses for the given destination are fetched from the Kubernetes Endpoints
// API.
func NewServer(
addr string,
config Config,
k8sAPI *k8s.API,
metadataAPI *k8s.MetadataAPI,
clusterStore *watcher.ClusterStore,
shutdown <-chan struct{},
) (*grpc.Server, error) {
log := logging.WithFields(logging.Fields{
"addr": addr,
"component": "server",
})
// Initialize indexers that are used across watchers
err := watcher.InitializeIndexers(k8sAPI)
if err != nil {
return nil, err
}
workloads, err := watcher.NewWorkloadWatcher(k8sAPI, metadataAPI, log, config.EnableEndpointSlices, config.DefaultOpaquePorts)
if err != nil {
return nil, err
}
endpoints, err := watcher.NewEndpointsWatcher(k8sAPI, metadataAPI, log, config.EnableEndpointSlices, "local")
if err != nil {
return nil, err
}
opaquePorts, err := watcher.NewOpaquePortsWatcher(k8sAPI, log, config.DefaultOpaquePorts)
if err != nil {
return nil, err
}
profiles, err := watcher.NewProfileWatcher(k8sAPI, log)
if err != nil {
return nil, err
}
srv := server{
pb.UnimplementedDestinationServer{},
config,
workloads,
endpoints,
opaquePorts,
profiles,
clusterStore,
k8sAPI,
metadataAPI,
log,
shutdown,
}
s := prometheus.NewGrpcServer(grpc.MaxConcurrentStreams(0))
// linkerd2-proxy-api/destination.Destination (proxy-facing)
pb.RegisterDestinationServer(s, &srv)
return s, nil
}
func (s *server) Get(dest *pb.GetDestination, stream pb.Destination_GetServer) error {
log := s.log
client, _ := peer.FromContext(stream.Context())
if client != nil {
log = log.WithField("remote", client.Addr)
}
var token contextToken
if dest.GetContextToken() != "" {
log.Debugf("Dest token: %q", dest.GetContextToken())
token = s.parseContextToken(dest.GetContextToken())
log = log.WithFields(logging.Fields{"context-pod": token.Pod, "context-ns": token.Ns})
}
log.Debugf("Get %s", dest.GetPath())
streamEnd := make(chan struct{})
// The host must be fully-qualified or be an IP address.
host, port, err := getHostAndPort(dest.GetPath())
if err != nil {
log.Debugf("Invalid service %s", dest.GetPath())
return status.Errorf(codes.InvalidArgument, "Invalid authority: %s", dest.GetPath())
}
// Return error for an IP query
if ip := net.ParseIP(host); ip != nil {
return status.Errorf(codes.InvalidArgument, "IP queries not supported by Get API: host=%s", host)
}
service, instanceID, err := parseK8sServiceName(host, s.config.ClusterDomain)
if err != nil {
log.Debugf("Invalid service %s", dest.GetPath())
return status.Errorf(codes.InvalidArgument, "Invalid authority: %s", dest.GetPath())
}
svc, err := s.k8sAPI.Svc().Lister().Services(service.Namespace).Get(service.Name)
if err != nil {
if kerrors.IsNotFound(err) {
log.Debugf("Service not found %s", service)
return status.Errorf(codes.NotFound, "Service %s.%s not found", service.Name, service.Namespace)
}
log.Debugf("Failed to get service %s: %v", service, err)
return status.Errorf(codes.Internal, "Failed to get service %s", dest.GetPath())
}
if cluster, found := svc.Labels[labels.RemoteDiscoveryLabel]; found {
// Remote discovery
remoteSvc, found := svc.Labels[labels.RemoteServiceLabel]
if !found {
log.Debugf("Remote discovery service missing remote service name %s", service)
return status.Errorf(codes.FailedPrecondition, "Remote discovery service missing remote service name %s", dest.GetPath())
}
remoteWatcher, remoteConfig, found := s.clusterStore.Get(cluster)
if !found {
log.Errorf("Failed to get remote cluster %s", cluster)
return status.Errorf(codes.NotFound, "Remote cluster not found: %s", cluster)
}
translator := newEndpointTranslator(
s.config.ControllerNS,
remoteConfig.TrustDomain,
s.config.EnableH2Upgrade,
false, // Disable endpoint filtering for remote discovery.
s.config.EnableIPv6,
s.config.ExtEndpointZoneWeights,
s.config.MeshedHttp2ClientParams,
fmt.Sprintf("%s.%s.svc.%s:%d", remoteSvc, service.Namespace, remoteConfig.ClusterDomain, port),
token.NodeName,
s.config.DefaultOpaquePorts,
s.metadataAPI,
stream,
streamEnd,
log,
)
translator.Start()
defer translator.Stop()
err = remoteWatcher.Subscribe(watcher.ServiceID{Namespace: service.Namespace, Name: remoteSvc}, port, instanceID, translator)
if err != nil {
var ise watcher.InvalidService
if errors.As(err, &ise) {
log.Debugf("Invalid remote discovery service %s", dest.GetPath())
return status.Errorf(codes.InvalidArgument, "Invalid authority: %s", dest.GetPath())
}
log.Errorf("Failed to subscribe to remote disocvery service %q in cluster %s: %s", dest.GetPath(), cluster, err)
return err
}
defer remoteWatcher.Unsubscribe(watcher.ServiceID{Namespace: service.Namespace, Name: remoteSvc}, port, instanceID, translator)
} else {
// Local discovery
translator := newEndpointTranslator(
s.config.ControllerNS,
s.config.IdentityTrustDomain,
s.config.EnableH2Upgrade,
true,
s.config.EnableIPv6,
s.config.ExtEndpointZoneWeights,
s.config.MeshedHttp2ClientParams,
dest.GetPath(),
token.NodeName,
s.config.DefaultOpaquePorts,
s.metadataAPI,
stream,
streamEnd,
log,
)
translator.Start()
defer translator.Stop()
err = s.endpoints.Subscribe(service, port, instanceID, translator)
if err != nil {
var ise watcher.InvalidService
if errors.As(err, &ise) {
log.Debugf("Invalid service %s", dest.GetPath())
return status.Errorf(codes.InvalidArgument, "Invalid authority: %s", dest.GetPath())
}
log.Errorf("Failed to subscribe to %s: %s", dest.GetPath(), err)
return err
}
defer s.endpoints.Unsubscribe(service, port, instanceID, translator)
}
select {
case <-s.shutdown:
case <-stream.Context().Done():
log.Debugf("Get %s cancelled", dest.GetPath())
case <-streamEnd:
log.Errorf("Get %s stream aborted", dest.GetPath())
}
return nil
}
func (s *server) GetProfile(dest *pb.GetDestination, stream pb.Destination_GetProfileServer) error {
log := s.log
client, _ := peer.FromContext(stream.Context())
if client != nil {
log = log.WithField("remote", client.Addr)
}
var token contextToken
if dest.GetContextToken() != "" {
log.Debugf("Dest token: %q", dest.GetContextToken())
token = s.parseContextToken(dest.GetContextToken())
log = log.WithFields(logging.Fields{"context-pod": token.Pod, "context-ns": token.Ns})
}
log.Debugf("Getting profile for %s", dest.GetPath())
// The host must be fully-qualified or be an IP address.
host, port, err := getHostAndPort(dest.GetPath())
if err != nil {
log.Debugf("Invalid address %q", dest.GetPath())
return status.Errorf(codes.InvalidArgument, "invalid authority: %q: %q", dest.GetPath(), err)
}
if ip := net.ParseIP(host); ip != nil {
err = s.getProfileByIP(token, ip, port, log, stream)
if err != nil {
var ise watcher.InvalidService
if errors.As(err, &ise) {
log.Debugf("Invalid service %s", dest.GetPath())
return status.Errorf(codes.InvalidArgument, "Invalid authority: %s", dest.GetPath())
}
log.Errorf("Failed to subscribe to profile by ip %q: %q", dest.GetPath(), err)
}
return err
}
err = s.getProfileByName(token, host, port, log, stream)
if err != nil {
var ise watcher.InvalidService
if errors.As(err, &ise) {
log.Debugf("Invalid service %s", dest.GetPath())
return status.Errorf(codes.InvalidArgument, "Invalid authority: %s", dest.GetPath())
}
log.Errorf("Failed to subscribe to profile by name %q: %q", dest.GetPath(), err)
}
return err
}
func (s *server) getProfileByIP(
token contextToken,
ip net.IP,
port uint32,
log *logging.Entry,
stream pb.Destination_GetProfileServer,
) error {
// Get the service that the IP currently maps to.
svcID, err := getSvcID(s.k8sAPI, ip.String(), s.log)
if err != nil {
return err
}
if svcID == nil {
return s.subscribeToEndpointProfile(nil, "", ip.String(), port, log, stream)
}
fqn := fmt.Sprintf("%s.%s.svc.%s", svcID.Name, svcID.Namespace, s.config.ClusterDomain)
return s.subscribeToServiceProfile(*svcID, token, fqn, port, log, stream)
}
func (s *server) getProfileByName(
token contextToken,
host string,
port uint32,
log *logging.Entry,
stream pb.Destination_GetProfileServer,
) error {
service, hostname, err := parseK8sServiceName(host, s.config.ClusterDomain)
if err != nil {
s.log.Debugf("Invalid service %s", host)
return status.Errorf(codes.InvalidArgument, "invalid service %q: %q", host, err)
}
// If the pod name (instance ID) is not empty, it means we parsed a DNS
// name. When we fetch the profile using a pod's DNS name, we want to
// return an endpoint in the profile response.
if hostname != "" {
return s.subscribeToEndpointProfile(&service, hostname, "", port, log, stream)
}
return s.subscribeToServiceProfile(service, token, host, port, log, stream)
}
// Resolves a profile for a service, sending updates to the provided stream.
//
// This function does not return until the stream is closed.
func (s *server) subscribeToServiceProfile(
service watcher.ID,
token contextToken,
fqn string,
port uint32,
log *logging.Entry,
stream pb.Destination_GetProfileServer,
) error {
log = log.
WithField("ns", service.Namespace).
WithField("svc", service.Name).
WithField("port", port)
canceled := stream.Context().Done()
streamEnd := make(chan struct{})
// We build up the pipeline of profile updaters backwards, starting from
// the translator which takes profile updates, translates them to protobuf
// and pushes them onto the gRPC stream.
translator := newProfileTranslator(stream, log, fqn, port, streamEnd)
translator.Start()
defer translator.Stop()
// The opaque ports adaptor merges profile updates with service opaque
// port annotation updates; it then publishes the result to the traffic
// split adaptor.
opaquePortsAdaptor := newOpaquePortsAdaptor(translator)
// Create an adaptor that merges service-level opaque port configurations
// onto profile updates.
err := s.opaquePorts.Subscribe(service, opaquePortsAdaptor)
if err != nil {
log.Warnf("Failed to subscribe to service updates for %s: %s", service, err)
return err
}
defer s.opaquePorts.Unsubscribe(service, opaquePortsAdaptor)
// Ensure that (1) nil values are turned into a default policy and (2)
// subsequent updates that refer to same service profile object are
// deduplicated to prevent sending redundant updates.
dup := newDedupProfileListener(opaquePortsAdaptor, log)
defaultProfile := sp.ServiceProfile{}
listener := newDefaultProfileListener(&defaultProfile, dup, log)
// The primary lookup uses the context token to determine the requester's
// namespace. If there's no namespace in the token, start a single
// subscription.
if token.Ns == "" {
return s.subscribeToServiceWithoutContext(fqn, listener, canceled, log, streamEnd)
}
return s.subscribeToServicesWithContext(fqn, token, listener, canceled, log, streamEnd)
}
// subscribeToServicesWithContext establishes two profile watches: a "backup"
// watch (ignoring the client namespace) and a preferred "primary" watch
// assuming the client's context. Once updates are received for both watches, we
// select over both watches to send profile updates to the stream. A nil update
// may be sent if both the primary and backup watches are initialized with a nil
// value.
func (s *server) subscribeToServicesWithContext(
fqn string,
token contextToken,
listener watcher.ProfileUpdateListener,
canceled <-chan struct{},
log *logging.Entry,
streamEnd <-chan struct{},
) error {
// We ned to support two subscriptions:
// - First, a backup subscription that assumes the context of the server
// namespace.
// - And then, a primary subscription that assumes the context of the client
// namespace.
primary, backup := newFallbackProfileListener(listener, log)
// The backup lookup ignores the context token to lookup any
// server-namespace-hosted profiles.
backupID, err := profileID(fqn, contextToken{}, s.config.ClusterDomain)
if err != nil {
log.Debug("Invalid service")
return status.Errorf(codes.InvalidArgument, "invalid profile ID: %s", err)
}
err = s.profiles.Subscribe(backupID, backup)
if err != nil {
log.Warnf("Failed to subscribe to profile: %s", err)
return err
}
defer s.profiles.Unsubscribe(backupID, backup)
primaryID, err := profileID(fqn, token, s.config.ClusterDomain)
if err != nil {
log.Debug("Invalid service")
return status.Errorf(codes.InvalidArgument, "invalid profile ID: %s", err)
}
err = s.profiles.Subscribe(primaryID, primary)
if err != nil {
log.Warnf("Failed to subscribe to profile: %s", err)
return err
}
defer s.profiles.Unsubscribe(primaryID, primary)
select {
case <-s.shutdown:
case <-canceled:
log.Debugf("GetProfile %s cancelled", fqn)
case <-streamEnd:
log.Errorf("GetProfile %s stream aborted", fqn)
}
return nil
}
// subscribeToServiceWithoutContext establishes a single profile watch, assuming
// no client context. All udpates are published to the provided listener.
func (s *server) subscribeToServiceWithoutContext(
fqn string,
listener watcher.ProfileUpdateListener,
canceled <-chan struct{},
log *logging.Entry,
streamEnd <-chan struct{},
) error {
id, err := profileID(fqn, contextToken{}, s.config.ClusterDomain)
if err != nil {
log.Debug("Invalid service")
return status.Errorf(codes.InvalidArgument, "invalid profile ID: %s", err)
}
err = s.profiles.Subscribe(id, listener)
if err != nil {
log.Warnf("Failed to subscribe to profile: %s", err)
return err
}
defer s.profiles.Unsubscribe(id, listener)
select {
case <-s.shutdown:
case <-canceled:
log.Debugf("GetProfile %s cancelled", fqn)
case <-streamEnd:
log.Errorf("GetProfile %s stream aborted", fqn)
}
return nil
}
// Resolves a profile for a single endpoint, sending updates to the provided
// stream.
//
// This function does not return until the stream is closed.
func (s *server) subscribeToEndpointProfile(
service *watcher.ServiceID,
hostname,
ip string,
port uint32,
log *logging.Entry,
stream pb.Destination_GetProfileServer,
) error {
canceled := stream.Context().Done()
streamEnd := make(chan struct{})
translator := newEndpointProfileTranslator(
s.config.EnableH2Upgrade,
s.config.ControllerNS,
s.config.IdentityTrustDomain,
s.config.DefaultOpaquePorts,
s.config.MeshedHttp2ClientParams,
stream,
streamEnd,
log,
)
translator.Start()
defer translator.Stop()
var err error
ip, err = s.workloads.Subscribe(service, hostname, ip, port, translator)
if err != nil {
return err
}
defer s.workloads.Unsubscribe(ip, port, translator)
select {
case <-s.shutdown:
case <-canceled:
s.log.Debugf("Cancelled")
case <-streamEnd:
log.Errorf("GetProfile %s:%d stream aborted", ip, port)
}
return nil
}
// getSvcID returns the service that corresponds to a Cluster IP address if one
// exists.
func getSvcID(k8sAPI *k8s.API, clusterIP string, log *logging.Entry) (*watcher.ServiceID, error) {
objs, err := k8sAPI.Svc().Informer().GetIndexer().ByIndex(watcher.PodIPIndex, clusterIP)
if err != nil {
return nil, status.Error(codes.Unknown, err.Error())
}
services := make([]*corev1.Service, 0)
for _, obj := range objs {
service := obj.(*corev1.Service)
services = append(services, service)
}
if len(services) > 1 {
conflictingServices := []string{}
for _, service := range services {
conflictingServices = append(conflictingServices, fmt.Sprintf("%s:%s", service.Namespace, service.Name))
}
log.Warnf("found conflicting %s cluster IP: %s", clusterIP, strings.Join(conflictingServices, ","))
return nil, status.Errorf(codes.FailedPrecondition, "found %d services with conflicting cluster IP %s", len(services), clusterIP)
}
if len(services) == 0 {
return nil, nil
}
service := &watcher.ServiceID{
Namespace: services[0].Namespace,
Name: services[0].Name,
}
return service, nil
}
////////////
/// util ///
////////////
type contextToken struct {
Ns string `json:"ns,omitempty"`
NodeName string `json:"nodeName,omitempty"`
Pod string `json:"pod,omitempty"`
}
func (s *server) parseContextToken(token string) contextToken {
ctxToken := contextToken{}
if token == "" {
return ctxToken
}
if err := json.Unmarshal([]byte(token), &ctxToken); err != nil {
// if json is invalid, means token can have ns:<namespace> form
parts := strings.Split(token, ":")
if len(parts) == 2 && parts[0] == "ns" {
s.log.Warnf("context token %s using old token format", token)
ctxToken = contextToken{
Ns: parts[1],
}
} else {
s.log.Errorf("context token %s is invalid: %s", token, err)
}
}
return ctxToken
}
func profileID(authority string, ctxToken contextToken, clusterDomain string) (watcher.ProfileID, error) {
host, _, err := getHostAndPort(authority)
if err != nil {
return watcher.ProfileID{}, fmt.Errorf("invalid authority: %w", err)
}
service, _, err := parseK8sServiceName(host, clusterDomain)
if err != nil {
return watcher.ProfileID{}, fmt.Errorf("invalid k8s service name: %w", err)
}
id := watcher.ProfileID{
Name: fmt.Sprintf("%s.%s.svc.%s", service.Name, service.Namespace, clusterDomain),
Namespace: service.Namespace,
}
if ctxToken.Ns != "" {
id.Namespace = ctxToken.Ns
}
return id, nil
}
func getHostAndPort(authority string) (string, watcher.Port, error) {
if !strings.Contains(authority, ":") {
return authority, watcher.Port(80), nil
}
host, sport, err := net.SplitHostPort(authority)
if err != nil {
return "", 0, fmt.Errorf("invalid destination: %w", err)
}
port, err := strconv.Atoi(sport)
if err != nil {
return "", 0, fmt.Errorf("invalid port %s: %w", sport, err)
}
if port <= 0 || port > 65535 {
return "", 0, fmt.Errorf("invalid port %d", port)
}
return host, watcher.Port(port), nil
}
type instanceID = string
// parseK8sServiceName is a utility that destructures a Kubernetes service hostname into its constituent components.
//
// If the authority does not represent a Kubernetes service, an error is returned.
//
// If the hostname is a pod DNS name, then the pod's name (instanceID) is returned
// as well. See https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/.
func parseK8sServiceName(fqdn, clusterDomain string) (watcher.ServiceID, instanceID, error) {
labels := strings.Split(fqdn, ".")
suffix := append([]string{"svc"}, strings.Split(clusterDomain, ".")...)
if !hasSuffix(labels, suffix) {
return watcher.ServiceID{}, "", fmt.Errorf("name %s does not match cluster domain %s", fqdn, clusterDomain)
}
n := len(labels)
if n == 2+len(suffix) {
// <service>.<namespace>.<suffix>
service := watcher.ServiceID{
Name: labels[0],
Namespace: labels[1],
}
return service, "", nil
}
if n == 3+len(suffix) {
// <instance-id>.<service>.<namespace>.<suffix>
instanceID := labels[0]
service := watcher.ServiceID{
Name: labels[1],
Namespace: labels[2],
}
return service, instanceID, nil
}
return watcher.ServiceID{}, "", fmt.Errorf("invalid k8s service %s", fqdn)
}
func hasSuffix(slice []string, suffix []string) bool {
if len(slice) < len(suffix) {
return false
}
for i, s := range slice[len(slice)-len(suffix):] {
if s != suffix[i] {
return false
}
}
return true
}
func getPodSkippedInboundPortsAnnotations(pod *corev1.Pod) map[uint32]struct{} {
annotation, ok := pod.Annotations[labels.ProxyIgnoreInboundPortsAnnotation]
if !ok || annotation == "" {
return nil
}
return util.ParsePorts(annotation)
}
package destination
import (
"sync"
"testing"
pb "github.com/linkerd/linkerd2-proxy-api/go/destination"
"github.com/linkerd/linkerd2/controller/api/destination/watcher"
"github.com/linkerd/linkerd2/controller/api/util"
l5dcrdclient "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned"
"github.com/linkerd/linkerd2/controller/k8s"
logging "github.com/sirupsen/logrus"
)
func makeServer(t *testing.T) *server {
t.Helper()
srv, _ := getServerWithClient(t)
return srv
}
func getServerWithClient(t *testing.T) (*server, l5dcrdclient.Interface) {
meshedPodResources := []string{`
apiVersion: v1
kind: Namespace
metadata:
name: ns`,
`
apiVersion: v1
kind: Service
metadata:
name: name1
namespace: ns
spec:
type: LoadBalancer
ipFamilies:
- IPv4
clusterIP: 172.17.12.0
clusterIPs:
- 172.17.12.0
ports:
- port: 8989`,
`
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: name1-ipv4
namespace: ns
labels:
kubernetes.io/service-name: name1
addressType: IPv4
endpoints:
- addresses:
- 172.17.0.12
targetRef:
kind: Pod
name: name1-1
namespace: ns
ports:
- port: 8989
protocol: TCP`,
`
apiVersion: v1
kind: Pod
metadata:
labels:
linkerd.io/control-plane-ns: linkerd
name: name1-1
namespace: ns
status:
phase: Running
conditions:
- type: Ready
status: "True"
podIP: 172.17.0.12
podIPs:
- ip: 172.17.0.12
spec:
containers:
- env:
- name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR
value: 0.0.0.0:4143
name: linkerd-proxy`,
`
apiVersion: v1
kind: Service
metadata:
name: name2
namespace: ns
spec:
type: LoadBalancer
clusterIP: 172.17.99.0
clusterIPs:
- 172.17.99.0
- 2001:db8::99
ports:
- port: 8989`,
`
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: name2-ipv4
namespace: ns
labels:
kubernetes.io/service-name: name2
addressType: IPv4
endpoints:
- addresses:
- 172.17.0.13
targetRef:
kind: Pod
name: name2-2
namespace: ns
ports:
- port: 8989
protocol: TCP`,
`
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: name2-ipv6
namespace: ns
labels:
kubernetes.io/service-name: name2
addressType: IPv6
endpoints:
- addresses:
- 2001:db8::78
targetRef:
kind: Pod
name: name2-2
namespace: ns
ports:
- port: 8989
protocol: TCP`,
`
apiVersion: v1
kind: Pod
metadata:
name: name2-2
namespace: ns
status:
phase: Succeeded
podIP: 172.17.0.13
podIPs:
- ip: 172.17.0.13
- ip: 2001:db8::78`,
`
apiVersion: v1
kind: Pod
metadata:
name: name2-3
namespace: ns
status:
phase: Failed
podIP: 172.17.0.13
podIPs:
- ip: 172.17.0.13`,
`
apiVersion: v1
kind: Pod
metadata:
name: name2-4
namespace: ns
deletionTimestamp: 2021-01-01T00:00:00Z
status:
podIP: 172.17.0.13
podIPs:
- ip: 172.17.0.13`,
`
apiVersion: linkerd.io/v1alpha2
kind: ServiceProfile
metadata:
name: name1.ns.svc.mycluster.local
namespace: ns
spec:
routes:
- name: route1
isRetryable: false
condition:
pathRegex: "/a/b/c"`,
}
clientSP := []string{
`
apiVersion: linkerd.io/v1alpha2
kind: ServiceProfile
metadata:
name: name1.ns.svc.mycluster.local
namespace: client-ns
spec:
routes:
- name: route2
isRetryable: true
condition:
pathRegex: "/x/y/z"`,
}
unmeshedPod := `
apiVersion: v1
kind: Pod
metadata:
name: name2
namespace: ns
status:
phase: Running
conditions:
- type: Ready
status: "True"
podIP: 172.17.0.13
podIPs:
- ip: 172.17.0.13`
meshedOpaquePodResources := []string{
`
apiVersion: v1
kind: Service
metadata:
name: name3
namespace: ns
spec:
type: LoadBalancer
clusterIP: 172.17.12.1
ports:
- port: 4242`,
`
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: name3
namespace: ns
labels:
kubernetes.io/service-name: name3
addressType: IPv4
endpoints:
- addresses:
- 172.17.0.14
targetRef:
kind: Pod
name: name3
namespace: ns
ports:
- port: 4242
protocol: TCP`,
`
apiVersion: v1
kind: Pod
metadata:
labels:
linkerd.io/control-plane-ns: linkerd
annotations:
config.linkerd.io/opaque-ports: "4242"
name: name3
namespace: ns
status:
phase: Running
conditions:
- type: Ready
status: "True"
podIP: 172.17.0.14
podIPs:
- ip: 172.17.0.14
spec:
containers:
- env:
- name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR
value: 0.0.0.0:4143
name: linkerd-proxy`,
}
meshedOpaqueServiceResources := []string{
`
apiVersion: v1
kind: Service
metadata:
name: name4
namespace: ns
annotations:
config.linkerd.io/opaque-ports: "4242"`,
}
meshedSkippedPodResource := []string{
`
apiVersion: v1
kind: Service
metadata:
name: name5
namespace: ns
spec:
type: LoadBalancer
clusterIP: 172.17.13.1
ports:
- port: 24224`,
`
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: name5
namespace: ns
labels:
kubernetes.io/service-name: name5
addressType: IPv4
endpoints:
- addresses:
- 172.17.0.15
targetRef:
kind: Pod
name: name5
namespace: ns
ports:
- port: 24224
protocol: TCP`,
`
apiVersion: v1
kind: Pod
metadata:
labels:
linkerd.io/control-plane-ns: linkerd
annotations:
config.linkerd.io/skip-inbound-ports: "24224"
name: name5
namespace: ns
status:
phase: Running
conditions:
- type: Ready
status: "True"
podIP: 172.17.0.15
podIPs:
- ip: 172.17.0.15
spec:
containers:
- env:
- name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR
value: 0.0.0.0:4143
name: linkerd-proxy`,
}
meshedStatefulSetPodResource := []string{
`
apiVersion: v1
kind: Service
metadata:
name: statefulset-svc
namespace: ns
spec:
type: LoadBalancer
clusterIP: 172.17.13.5
ports:
- port: 8989`,
`
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: statefulset-svc
namespace: ns
labels:
kubernetes.io/service-name: statefulset-svc
addressType: IPv4
endpoints:
- addresses:
- 172.17.13.14 # Endpoint without a targetRef or hostname
- addresses:
- 172.17.13.15
hostname: pod-0
targetRef:
kind: Pod
name: pod-0
namespace: ns
ports:
- port: 8989
protocol: TCP`,
`
apiVersion: v1
kind: Pod
metadata:
labels:
linkerd.io/control-plane-ns: linkerd
name: pod-0
namespace: ns
status:
phase: Running
conditions:
- type: Ready
status: "True"
podIP: 172.17.13.15
podIPs:
- ip: 172.17.13.15`,
}
policyResources := []string{
`
apiVersion: v1
kind: Service
metadata:
name: policy-test
namespace: ns
spec:
type: LoadBalancer
clusterIP: 172.17.12.2
ports:
- port: 80`,
`
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: policy-test
namespace: ns
labels:
kubernetes.io/service-name: policy-test
addressType: IPv4
endpoints:
- addresses:
- 172.17.0.16
targetRef:
kind: Pod
name: policy-test
namespace: ns
ports:
- port: 80
protocol: TCP`,
`
apiVersion: v1
kind: Pod
metadata:
labels:
linkerd.io/control-plane-ns: linkerd
app: policy-test
name: policy-test
namespace: ns
status:
phase: Running
conditions:
- type: Ready
status: "True"
podIP: 172.17.0.16
podIPs:
- ip: 172.17.0.16
spec:
containers:
- name: linkerd-proxy
env:
- name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR
value: 0.0.0.0:4143
- name: app
image: nginx
ports:
- containerPort: 80
name: http
protocol: TCP`,
`
apiVersion: policy.linkerd.io/v1beta2
kind: Server
metadata:
name: policy-test
namespace: ns
spec:
podSelector:
matchLabels:
app: policy-test
port: 80
proxyProtocol: opaque`,
`
apiVersion: policy.linkerd.io/v1beta2
kind: Server
metadata:
name: policy-test-external-workload
namespace: ns
spec:
externalWorkloadSelector:
matchLabels:
app: external-workload-policy-test
port: 80
proxyProtocol: opaque`,
}
policyResourcesNativeSidecar := []string{
`
apiVersion: v1
kind: Service
metadata:
name: native
namespace: ns
spec:
type: LoadBalancer
clusterIP: 172.17.12.4
ports:
- port: 80`,
`
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: native
namespace: ns
labels:
kubernetes.io/service-name: native
addressType: IPv4
endpoints:
- addresses:
- 172.17.0.18
targetRef:
kind: Pod
name: native
namespace: ns
ports:
- port: 80
protocol: TCP`,
`
apiVersion: v1
kind: Pod
metadata:
labels:
linkerd.io/control-plane-ns: linkerd
app: native
name: native
namespace: ns
status:
phase: Running
conditions:
- type: Ready
status: "True"
podIP: 172.17.0.18
podIPs:
- ip: 172.17.0.18
spec:
initContainers:
- name: linkerd-proxy
env:
- name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR
value: 0.0.0.0:4143
- name: app
image: nginx
ports:
- containerPort: 80
name: http
protocol: TCP`,
`
apiVersion: policy.linkerd.io/v1beta2
kind: Server
metadata:
name: native
namespace: ns
spec:
podSelector:
matchLabels:
app: native
port: 80
proxyProtocol: opaque`,
}
hostPortMapping := []string{
`
kind: Pod
apiVersion: v1
metadata:
name: hostport-mapping
namespace: ns
status:
phase: Running
conditions:
- type: Ready
status: "True"
hostIP: 192.168.1.20
podIP: 172.17.0.17
podIPs:
- ip: 172.17.0.17
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
hostPort: 7777
name: nginx-7777`,
}
exportedServiceResources := []string{`
apiVersion: v1
kind: Namespace
metadata:
name: ns`,
`
apiVersion: v1
kind: Service
metadata:
name: foo
namespace: ns
spec:
type: LoadBalancer
ports:
- port: 80`,
`
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: foo
namespace: ns
labels:
kubernetes.io/service-name: foo
addressType: IPv4
endpoints:
- addresses:
- 172.17.55.1
targetRef:
kind: Pod
name: foo-1
namespace: ns
ports:
- port: 80
protocol: TCP`,
`
apiVersion: v1
kind: Pod
metadata:
labels:
linkerd.io/control-plane-ns: linkerd
name: foo-1
namespace: ns
status:
phase: Running
conditions:
- type: Ready
status: "True"
podIP: 172.17.55.1
podIPs:
- ip: 172.17.55.1
spec:
containers:
- env:
- name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR
value: 0.0.0.0:4143
name: linkerd-proxy`,
}
destinationCredentialsResources := []string{`
apiVersion: v1
data:
kubeconfig: V2UncmUgbm8gc3RyYW5nZXJzIHRvIGxvdmUKWW91IGtub3cgdGhlIHJ1bGVzIGFuZCBzbyBkbyBJIChkbyBJKQpBIGZ1bGwgY29tbWl0bWVudCdzIHdoYXQgSSdtIHRoaW5raW5nIG9mCllvdSB3b3VsZG4ndCBnZXQgdGhpcyBmcm9tIGFueSBvdGhlciBndXkKSSBqdXN0IHdhbm5hIHRlbGwgeW91IGhvdyBJJ20gZmVlbGluZwpHb3R0YSBtYWtlIHlvdSB1bmRlcnN0YW5kCk5ldmVyIGdvbm5hIGdpdmUgeW91IHVwCk5ldmVyIGdvbm5hIGxldCB5b3UgZG93bgpOZXZlciBnb25uYSBydW4gYXJvdW5kIGFuZCBkZXNlcnQgeW91Ck5ldmVyIGdvbm5hIG1ha2UgeW91IGNyeQpOZXZlciBnb25uYSBzYXkgZ29vZGJ5ZQpOZXZlciBnb25uYSB0ZWxsIGEgbGllIGFuZCBodXJ0IHlvdQpXZSd2ZSBrbm93biBlYWNoIG90aGVyIGZvciBzbyBsb25nCllvdXIgaGVhcnQncyBiZWVuIGFjaGluZywgYnV0IHlvdSdyZSB0b28gc2h5IHRvIHNheSBpdCAoc2F5IGl0KQpJbnNpZGUsIHdlIGJvdGgga25vdyB3aGF0J3MgYmVlbiBnb2luZyBvbiAoZ29pbmcgb24pCldlIGtub3cgdGhlIGdhbWUgYW5kIHdlJ3JlIGdvbm5hIHBsYXkgaXQKQW5kIGlmIHlvdSBhc2sgbWUgaG93IEknbSBmZWVsaW5nCkRvbid0IHRlbGwgbWUgeW91J3JlIHRvbyBibGluZCB0byBzZWUKTmV2ZXIgZ29ubmEgZ2l2ZSB5b3UgdXAKTmV2ZXIgZ29ubmEgbGV0IHlvdSBkb3duCk5ldmVyIGdvbm5hIHJ1biBhcm91bmQgYW5kIGRlc2VydCB5b3UKTmV2ZXIgZ29ubmEgbWFrZSB5b3UgY3J5Ck5ldmVyIGdvbm5hIHNheSBnb29kYnllCk5ldmVyIGdvbm5hIHRlbGwgYSBsaWUgYW5kIGh1cnQgeW91
kind: Secret
metadata:
annotations:
multicluster.linkerd.io/cluster-domain: cluster.local
multicluster.linkerd.io/trust-domain: cluster.local
labels:
multicluster.linkerd.io/cluster-name: target
name: cluster-credentials-target
namespace: linkerd
type: mirror.linkerd.io/remote-kubeconfig`}
mirrorServiceResources := []string{`
apiVersion: v1
kind: Service
metadata:
name: foo-target
namespace: ns
labels:
multicluster.linkerd.io/remote-discovery: target
multicluster.linkerd.io/remote-service: foo
spec:
type: LoadBalancer
ports:
- port: 80`,
}
externalWorkloads := []string{`
apiVersion: workload.linkerd.io/v1beta1
kind: ExternalWorkload
metadata:
name: my-cool-workload
namespace: ns
annotations:
config.linkerd.io/opaque-ports: "4242"
spec:
meshTLS:
identity: spiffe://some-domain/cool
serverName: server.local
workloadIPs:
- ip: 200.1.1.1
ports:
- port: 8989
- port: 4242
- name: linkerd-proxy
port: 4143
status:
conditions:
- ready: true`,
`
apiVersion: workload.linkerd.io/v1beta1
kind: ExternalWorkload
metadata:
name: policy-test-workload
namespace: ns
labels:
app: external-workload-policy-test
spec:
meshTLS:
identity: spiffe://some-domain/cool
serverName: server.local
workloadIPs:
- ip: 200.1.1.2
ports:
- port: 80
- name: linkerd-proxy
port: 4143
status:
conditions:
ready: true`,
`
apiVersion: v1
kind: Service
metadata:
name: policy-test-external-workload
namespace: ns
spec:
type: LoadBalancer
clusterIP: 172.17.12.3
ports:
- port: 80`,
`
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: policy-test-external-workload
namespace: ns
labels:
kubernetes.io/service-name: policy-test-external-workload
addressType: IPv4
endpoints:
- addresses:
- 200.1.1.2
targetRef:
kind: ExternalWorkload
name: policy-test-workload
namespace: ns
ports:
- port: 80
protocol: TCP`,
}
externalNameResources := []string{
`
apiVersion: v1
kind: Service
metadata:
name: externalname
namespace: ns
spec:
type: ExternalName
externalName: linkerd.io`,
}
ipv6 := []string{
`
apiVersion: v1
kind: Service
metadata:
name: name-ipv6
namespace: ns
spec:
type: ClusterIP
ipFamilies:
- IPv6
clusterIP: 2001:db8::93
clusterIPs:
- 2001:db8::93
ports:
- port: 8989`,
`
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: name-ipv6
namespace: ns
labels:
kubernetes.io/service-name: name-ipv6
addressType: IPv6
endpoints:
- addresses:
- 2001:db8::68
targetRef:
kind: Pod
name: name-ipv6
namespace: ns
ports:
- port: 8989
protocol: TCP`,
`
apiVersion: v1
kind: Pod
metadata:
labels:
linkerd.io/control-plane-ns: linkerd
name: name-ipv6
namespace: ns
status:
phase: Running
conditions:
- type: Ready
status: "True"
podIP: 2001:db8::68
podIPs:
- ip: 2001:db8::68
spec:
containers:
- env:
- name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR
value: 0.0.0.0:4143
name: linkerd-proxy`,
}
dualStack := []string{
`
apiVersion: v1
kind: Service
metadata:
name: name-ds
namespace: ns
spec:
type: ClusterIP
ipFamilies:
- IPv4
- IPv6
clusterIP: 172.17.13.0
clusterIPs:
- 172.17.13.0
- 2001:db8::88
ports:
- port: 8989`,
`
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: name-ds-ipv4
namespace: ns
labels:
kubernetes.io/service-name: name-ds
addressType: IPv4
endpoints:
- addresses:
- 172.17.0.19
targetRef:
kind: Pod
name: name-ds
namespace: ns
ports:
- port: 8989
protocol: TCP`,
`
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: name-ds-ipv6
namespace: ns
labels:
kubernetes.io/service-name: name-ds
addressType: IPv6
endpoints:
- addresses:
- 2001:db8::94
targetRef:
kind: Pod
name: name-ds
namespace: ns
ports:
- port: 8989
protocol: TCP`,
`
apiVersion: v1
kind: Pod
metadata:
labels:
linkerd.io/control-plane-ns: linkerd
name: name-ds
namespace: ns
status:
phase: Running
conditions:
- type: Ready
status: "True"
podIP: 172.17.0.19
podIPs:
- ip: 172.17.0.19
- ip: 2001:db8::94
spec:
containers:
- env:
- name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR
value: 0.0.0.0:4143
name: linkerd-proxy`,
`
apiVersion: linkerd.io/v1alpha2
kind: ServiceProfile
metadata:
name: name-ds.ns.svc.mycluster.local
namespace: ns
spec:
routes:
- name: route1
isRetryable: false
condition:
pathRegex: "/a/b/c"`,
}
res := append(meshedPodResources, clientSP...)
res = append(res, unmeshedPod)
res = append(res, meshedOpaquePodResources...)
res = append(res, meshedOpaqueServiceResources...)
res = append(res, meshedSkippedPodResource...)
res = append(res, meshedStatefulSetPodResource...)
res = append(res, policyResources...)
res = append(res, policyResourcesNativeSidecar...)
res = append(res, hostPortMapping...)
res = append(res, mirrorServiceResources...)
res = append(res, destinationCredentialsResources...)
res = append(res, externalWorkloads...)
res = append(res, externalNameResources...)
res = append(res, ipv6...)
res = append(res, dualStack...)
k8sAPI, l5dClient, err := k8s.NewFakeAPIWithL5dClient(res...)
if err != nil {
t.Fatalf("NewFakeAPIWithL5dClient returned an error: %s", err)
}
metadataAPI, err := k8s.NewFakeMetadataAPI(nil)
if err != nil {
t.Fatalf("NewFakeMetadataAPI returned an error: %s", err)
}
log := logging.WithField("test", t.Name())
// logging.SetLevel(logging.TraceLevel)
defaultOpaquePorts := map[uint32]struct{}{
25: {},
443: {},
587: {},
3306: {},
5432: {},
11211: {},
}
err = watcher.InitializeIndexers(k8sAPI)
if err != nil {
t.Fatalf("initializeIndexers returned an error: %s", err)
}
workloads, err := watcher.NewWorkloadWatcher(k8sAPI, metadataAPI, log, true, defaultOpaquePorts)
if err != nil {
t.Fatalf("can't create Workloads watcher: %s", err)
}
endpoints, err := watcher.NewEndpointsWatcher(k8sAPI, metadataAPI, log, true, "local")
if err != nil {
t.Fatalf("can't create Endpoints watcher: %s", err)
}
opaquePorts, err := watcher.NewOpaquePortsWatcher(k8sAPI, log, defaultOpaquePorts)
if err != nil {
t.Fatalf("can't create opaque ports watcher: %s", err)
}
profiles, err := watcher.NewProfileWatcher(k8sAPI, log)
if err != nil {
t.Fatalf("can't create profile watcher: %s", err)
}
clusterStore, err := watcher.NewClusterStoreWithDecoder(k8sAPI.Client, "linkerd", true, watcher.CreateMockDecoder(exportedServiceResources...))
if err != nil {
t.Fatalf("can't create cluster store: %s", err)
}
// Sync after creating watchers so that the indexers added get updated
// properly
k8sAPI.Sync(nil)
metadataAPI.Sync(nil)
clusterStore.Sync(nil)
return &server{
pb.UnimplementedDestinationServer{},
Config{
EnableH2Upgrade: true,
EnableIPv6: true,
ControllerNS: "linkerd",
ClusterDomain: "mycluster.local",
IdentityTrustDomain: "trust.domain",
DefaultOpaquePorts: defaultOpaquePorts,
},
workloads,
endpoints,
opaquePorts,
profiles,
clusterStore,
k8sAPI,
metadataAPI,
log,
make(<-chan struct{}),
}, l5dClient
}
type bufferingGetStream struct {
updates chan *pb.Update
util.MockServerStream
}
func (bgs *bufferingGetStream) Send(update *pb.Update) error {
bgs.updates <- update
return nil
}
type bufferingGetProfileStream struct {
updates []*pb.DestinationProfile
util.MockServerStream
mu sync.Mutex
}
func (bgps *bufferingGetProfileStream) Send(profile *pb.DestinationProfile) error {
bgps.mu.Lock()
defer bgps.mu.Unlock()
bgps.updates = append(bgps.updates, profile)
return nil
}
func (bgps *bufferingGetProfileStream) Updates() []*pb.DestinationProfile {
bgps.mu.Lock()
defer bgps.mu.Unlock()
return bgps.updates
}
type mockDestinationGetServer struct {
util.MockServerStream
updatesReceived chan *pb.Update
}
func (m *mockDestinationGetServer) Send(update *pb.Update) error {
m.updatesReceived <- update
return nil
}
type mockDestinationGetProfileServer struct {
util.MockServerStream
profilesReceived chan *pb.DestinationProfile
}
func (m *mockDestinationGetProfileServer) Send(profile *pb.DestinationProfile) error {
m.profilesReceived <- profile
return nil
}
func makeEndpointTranslator(t *testing.T) (*mockDestinationGetServer, *endpointTranslator) {
t.Helper()
node := `apiVersion: v1
kind: Node
metadata:
annotations:
kubeadm.alpha.kubernetes.io/cri-socket: /run/containerd/containerd.sock
node.alpha.kubernetes.io/ttl: "0"
labels:
beta.kubernetes.io/arch: amd64
kubernetes.io/os: linux
kubernetes.io/arch: amd64
kubernetes.io/hostname: kind-worker
kubernetes.io/os: linux
topology.kubernetes.io/region: west
topology.kubernetes.io/zone: west-1a
name: test-123
`
metadataAPI, err := k8s.NewFakeMetadataAPI([]string{node})
if err != nil {
t.Fatalf("NewFakeMetadataAPI returned an error: %s", err)
}
metadataAPI.Sync(nil)
mockGetServer := &mockDestinationGetServer{updatesReceived: make(chan *pb.Update, 50)}
translator := newEndpointTranslator(
"linkerd",
"trust.domain",
true,
true,
true, // enableEndpointFiltering
false, // extEndpointZoneWeights
nil, // meshedHttp2ClientParams
"service-name.service-ns",
"test-123",
map[uint32]struct{}{},
metadataAPI,
mockGetServer,
nil,
logging.WithField("test", t.Name()),
)
return mockGetServer, translator
}
package watcher
import (
"context"
"errors"
"fmt"
"sync"
"github.com/linkerd/linkerd2/controller/k8s"
pkgK8s "github.com/linkerd/linkerd2/pkg/k8s"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
logging "github.com/sirupsen/logrus"
v1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/clientcmd"
)
type (
// ClusterStore indexes clusters in which remote service discovery may be
// performed. For each store item, an EndpointsWatcher is created to read
// state directly from the respective cluster's API Server. In addition,
// each store item has some associated and immutable configuration that is
// required for service discovery.
ClusterStore struct {
// Protects against illegal accesses
sync.RWMutex
api *k8s.API
store map[string]remoteCluster
enableEndpointSlices bool
log *logging.Entry
// Function used to parse a kubeconfig from a byte buffer. Based on the
// kubeconfig, it creates API Server clients
decodeFn configDecoder
size_gauge prometheus.GaugeFunc
}
// remoteCluster is a helper struct that represents a store item
remoteCluster struct {
watcher *EndpointsWatcher
config clusterConfig
// Used to signal shutdown to the associated watcher's informers
stopCh chan<- struct{}
}
// clusterConfig holds immutable configuration for a given cluster
clusterConfig struct {
TrustDomain string
ClusterDomain string
}
// configDecoder is the type of a function that given a byte buffer, returns
// a pair of API Server clients. The cache uses this function to dynamically
// create clients after discovering a Secret.
configDecoder = func(data []byte, cluster string, enableEndpointSlices bool) (*k8s.API, *k8s.MetadataAPI, error)
)
const (
clusterNameLabel = "multicluster.linkerd.io/cluster-name"
trustDomainAnnotation = "multicluster.linkerd.io/trust-domain"
clusterDomainAnnotation = "multicluster.linkerd.io/cluster-domain"
)
// NewClusterStore creates a new (empty) ClusterStore. It
// requires a Kubernetes API Server client instantiated for the local cluster.
//
// When created, a pair of event handlers are registered for the local cluster's
// Secret informer. The event handlers are responsible for driving the discovery
// of remote clusters and their configuration
func NewClusterStore(client kubernetes.Interface, namespace string, enableEndpointSlices bool) (*ClusterStore, error) {
return NewClusterStoreWithDecoder(client, namespace, enableEndpointSlices, decodeK8sConfigFromSecret)
}
func (cs *ClusterStore) Sync(stopCh <-chan struct{}) {
cs.api.Sync(stopCh)
}
func (cs *ClusterStore) UnregisterGauges() {
prometheus.Unregister(cs.size_gauge)
}
// newClusterStoreWithDecoder is a helper function that allows the creation of a
// store with an arbitrary `configDecoder` function.
func NewClusterStoreWithDecoder(client kubernetes.Interface, namespace string, enableEndpointSlices bool, decodeFn configDecoder) (*ClusterStore, error) {
api := k8s.NewNamespacedAPI(client, nil, nil, namespace, "local", k8s.Secret)
cs := &ClusterStore{
store: make(map[string]remoteCluster),
log: logging.WithFields(logging.Fields{
"component": "cluster-store",
}),
enableEndpointSlices: enableEndpointSlices,
api: api,
decodeFn: decodeFn,
}
cs.size_gauge = promauto.NewGaugeFunc(prometheus.GaugeOpts{
Name: "cluster_store_size",
Help: "The number of linked clusters in the remote discovery cluster store",
}, func() float64 { return (float64)(len(cs.store)) })
_, err := cs.api.Secret().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
secret, ok := obj.(*v1.Secret)
if !ok {
cs.log.Errorf("Error processing 'Secret' object: got %#v, expected *corev1.Secret", secret)
return
}
if secret.Type != pkgK8s.MirrorSecretType {
cs.log.Tracef("Skipping Add event for 'Secret' object %s/%s: invalid type %s", secret.Namespace, secret.Name, secret.Type)
return
}
clusterName, found := secret.GetLabels()[clusterNameLabel]
if !found {
cs.log.Tracef("Skipping Add event for 'Secret' object %s/%s: missing \"%s\" label", secret.Namespace, secret.Name, clusterNameLabel)
return
}
if err := cs.addCluster(clusterName, secret); err != nil {
cs.log.Errorf("Error adding cluster %s to store: %v", clusterName, err)
}
},
DeleteFunc: func(obj interface{}) {
secret, ok := obj.(*v1.Secret)
if !ok {
// If the Secret was deleted when the watch was disconnected
// (for whatever reason) and the event was missed, the object is
// added with 'DeletedFinalStateUnknown'. Its state may be
// stale, so it should be cleaned-up.
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
if !ok {
cs.log.Debugf("Unable to get object from DeletedFinalStateUnknown %#v", obj)
return
}
// If the zombie object is a `Secret` we can delete it.
secret, ok = tombstone.Obj.(*v1.Secret)
if !ok {
cs.log.Debugf("DeletedFinalStateUnknown contained object that is not a Secret %#v", obj)
return
}
}
clusterName, found := secret.GetLabels()[clusterNameLabel]
if !found {
cs.log.Tracef("Skipping Delete event for 'Secret' object %s/%s: missing \"%s\" label", secret.Namespace, secret.Name, clusterNameLabel)
return
}
cs.removeCluster(clusterName)
},
})
if err != nil {
return nil, err
}
return cs, nil
}
// Get safely retrieves a store item given a cluster name.
func (cs *ClusterStore) Get(clusterName string) (*EndpointsWatcher, clusterConfig, bool) {
cs.RLock()
defer cs.RUnlock()
cw, found := cs.store[clusterName]
return cw.watcher, cw.config, found
}
// removeCluster is triggered by the cache's Secret informer when a secret is
// removed. Given a cluster name, it removes the entry from the cache after
// stopping the associated watcher.
func (cs *ClusterStore) removeCluster(clusterName string) {
cs.Lock()
defer cs.Unlock()
r, found := cs.store[clusterName]
if !found {
return
}
r.watcher.removeHandlers()
r.watcher.k8sAPI.UnregisterGauges()
r.watcher.metadataAPI.UnregisterGauges()
close(r.stopCh)
delete(cs.store, clusterName)
cs.log.Infof("Removed cluster %s from ClusterStore", clusterName)
}
// addCluster is triggered by the cache's Secret informer when a secret is
// discovered for the first time. Given a cluster name and a Secret
// object, it creates an EndpointsWatcher for a remote cluster and syncs its
// informers before returning.
func (cs *ClusterStore) addCluster(clusterName string, secret *v1.Secret) error {
data, found := secret.Data[pkgK8s.ConfigKeyName]
if !found {
return errors.New("missing kubeconfig file")
}
clusterDomain, found := secret.GetAnnotations()[clusterDomainAnnotation]
if !found {
return fmt.Errorf("missing \"%s\" annotation", clusterDomainAnnotation)
}
trustDomain, found := secret.GetAnnotations()[trustDomainAnnotation]
if !found {
return fmt.Errorf("missing \"%s\" annotation", trustDomainAnnotation)
}
remoteAPI, metadataAPI, err := cs.decodeFn(data, clusterName, cs.enableEndpointSlices)
if err != nil {
return err
}
stopCh := make(chan struct{}, 1)
watcher, err := NewEndpointsWatcher(
remoteAPI,
metadataAPI,
logging.WithFields(logging.Fields{
"remote-cluster": clusterName,
}),
cs.enableEndpointSlices,
clusterName,
)
if err != nil {
return err
}
cs.Lock()
defer cs.Unlock()
cs.store[clusterName] = remoteCluster{
watcher,
clusterConfig{
trustDomain,
clusterDomain,
},
stopCh,
}
go remoteAPI.Sync(stopCh)
go metadataAPI.Sync(stopCh)
cs.log.Infof("Added cluster %s to ClusterStore", clusterName)
return nil
}
// decodeK8sConfigFromSecret implements the decoder function type. Given a byte
// buffer, it attempts to parse it as a kubeconfig file. If successful, returns
// a pair of API Server clients.
func decodeK8sConfigFromSecret(data []byte, cluster string, enableEndpointSlices bool) (*k8s.API, *k8s.MetadataAPI, error) {
cfg, err := clientcmd.RESTConfigFromKubeConfig(data)
if err != nil {
return nil, nil, err
}
ctx := context.Background()
var remoteAPI *k8s.API
if enableEndpointSlices {
remoteAPI, err = k8s.InitializeAPIForConfig(
ctx,
cfg,
true,
cluster,
k8s.ES, k8s.Pod, k8s.Svc, k8s.Srv,
)
} else {
remoteAPI, err = k8s.InitializeAPIForConfig(
ctx,
cfg,
true,
cluster,
k8s.Endpoint, k8s.Pod, k8s.Svc, k8s.Srv,
)
}
if err != nil {
return nil, nil, err
}
metadataAPI, err := k8s.InitializeMetadataAPIForConfig(cfg, cluster, k8s.RS)
if err != nil {
return nil, nil, err
}
return remoteAPI, metadataAPI, nil
}
package watcher
import (
"context"
"fmt"
"net"
"sort"
"strconv"
"strings"
"sync"
"time"
ewv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1"
"github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta2"
"github.com/linkerd/linkerd2/controller/k8s"
consts "github.com/linkerd/linkerd2/pkg/k8s"
"github.com/prometheus/client_golang/prometheus"
logging "github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"
discovery "k8s.io/api/discovery/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/tools/cache"
)
const (
// metrics labels
service = "service"
namespace = "namespace"
targetCluster = "target_cluster"
targetService = "target_service"
targetServiceNamespace = "target_service_namespace"
opaqueProtocol = "opaque"
)
const endpointTargetRefPod = "Pod"
const endpointTargetRefExternalWorkload = "ExternalWorkload"
type (
// Address represents an individual port on a specific endpoint.
// This endpoint might be the result of a the existence of a pod
// that is targeted by this service; alternatively it can be the
// case that this endpoint is not associated with a pod and maps
// to some other IP (i.e. a remote service gateway)
Address struct {
IP string
Port Port
Pod *corev1.Pod
ExternalWorkload *ewv1beta1.ExternalWorkload
OwnerName string
OwnerKind string
Identity string
AuthorityOverride string
Zone *string
ForZones []discovery.ForZone
OpaqueProtocol bool
}
// AddressSet is a set of Address, indexed by ID.
// The ID can be either:
// 1) A reference to service: id.Name contains both the service name and
// the target IP and port (see newServiceRefAddress)
// 2) A reference to a pod: id.Name refers to the pod's name, and
// id.IPFamily refers to the ES AddressType (see newPodRefAddress).
// 3) A reference to an ExternalWorkload: id.Name refers to the EW's name.
AddressSet struct {
Addresses map[ID]Address
Labels map[string]string
LocalTrafficPolicy bool
}
portAndHostname struct {
port Port
hostname string
}
// EndpointsWatcher watches all endpoints and services in the Kubernetes
// cluster. Listeners can subscribe to a particular service and port and
// EndpointsWatcher will publish the address set and all future changes for
// that service:port.
EndpointsWatcher struct {
publishers map[ServiceID]*servicePublisher
k8sAPI *k8s.API
metadataAPI *k8s.MetadataAPI
cluster string
log *logging.Entry
enableEndpointSlices bool
sync.RWMutex // This mutex protects modification of the map itself.
informerHandlers
}
// informerHandlers holds a registration handle for each informer handler
// that has been registered for the EndpointsWatcher. The registration
// handles are used to re-deregister informer handlers when the
// EndpointsWatcher stops.
informerHandlers struct {
epHandle cache.ResourceEventHandlerRegistration
svcHandle cache.ResourceEventHandlerRegistration
srvHandle cache.ResourceEventHandlerRegistration
}
// servicePublisher represents a service. It keeps a map of portPublishers
// keyed by port and hostname. This is because each watch on a service
// will have a port and optionally may specify a hostname. The port
// and hostname will influence the endpoint set which is why a separate
// portPublisher is required for each port and hostname combination. The
// service's port mapping will be applied to the requested port and the
// mapped port will be used in the addresses set. If a hostname is
// requested, the address set will be filtered to only include addresses
// with the requested hostname.
servicePublisher struct {
id ServiceID
log *logging.Entry
k8sAPI *k8s.API
metadataAPI *k8s.MetadataAPI
enableEndpointSlices bool
localTrafficPolicy bool
cluster string
ports map[portAndHostname]*portPublisher
// All access to the servicePublisher and its portPublishers is explicitly synchronized by
// this mutex.
sync.Mutex
}
// portPublisher represents a service along with a port and optionally a
// hostname. Multiple listeners may be subscribed to a portPublisher.
// portPublisher maintains the current state of the address set and
// publishes diffs to all listeners when updates come from either the
// endpoints API or the service API.
portPublisher struct {
id ServiceID
targetPort namedPort
srcPort Port
hostname string
log *logging.Entry
k8sAPI *k8s.API
metadataAPI *k8s.MetadataAPI
enableEndpointSlices bool
exists bool
addresses AddressSet
listeners []EndpointUpdateListener
metrics endpointsMetrics
localTrafficPolicy bool
}
// EndpointUpdateListener is the interface that subscribers must implement.
EndpointUpdateListener interface {
Add(set AddressSet)
Remove(set AddressSet)
NoEndpoints(exists bool)
}
)
var endpointsVecs = newEndpointsMetricsVecs()
var undefinedEndpointPort = Port(0)
// shallowCopy returns a shallow copy of addr, in the sense that the Pod and
// ExternalWorkload fields of the Addresses map values still point to the
// locations of the original variable
func (addr AddressSet) shallowCopy() AddressSet {
addresses := make(map[ID]Address)
for k, v := range addr.Addresses {
addresses[k] = v
}
labels := make(map[string]string)
for k, v := range addr.Labels {
labels[k] = v
}
return AddressSet{
Addresses: addresses,
Labels: labels,
LocalTrafficPolicy: addr.LocalTrafficPolicy,
}
}
// NewEndpointsWatcher creates an EndpointsWatcher and begins watching the
// k8sAPI for pod, service, and endpoint changes. An EndpointsWatcher will
// watch on Endpoints or EndpointSlice resources, depending on cluster configuration.
func NewEndpointsWatcher(k8sAPI *k8s.API, metadataAPI *k8s.MetadataAPI, log *logging.Entry, enableEndpointSlices bool, cluster string) (*EndpointsWatcher, error) {
ew := &EndpointsWatcher{
publishers: make(map[ServiceID]*servicePublisher),
k8sAPI: k8sAPI,
metadataAPI: metadataAPI,
enableEndpointSlices: enableEndpointSlices,
cluster: cluster,
log: log.WithFields(logging.Fields{
"component": "endpoints-watcher",
}),
}
var err error
ew.svcHandle, err = k8sAPI.Svc().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: ew.addService,
DeleteFunc: ew.deleteService,
UpdateFunc: ew.updateService,
})
if err != nil {
return nil, err
}
ew.srvHandle, err = k8sAPI.Srv().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: ew.addServer,
DeleteFunc: ew.deleteServer,
UpdateFunc: ew.updateServer,
})
if err != nil {
return nil, err
}
if ew.enableEndpointSlices {
ew.log.Debugf("Watching EndpointSlice resources")
ew.epHandle, err = k8sAPI.ES().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: ew.addEndpointSlice,
DeleteFunc: ew.deleteEndpointSlice,
UpdateFunc: ew.updateEndpointSlice,
})
if err != nil {
return nil, err
}
} else {
ew.log.Debugf("Watching Endpoints resources")
ew.epHandle, err = k8sAPI.Endpoint().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: ew.addEndpoints,
DeleteFunc: ew.deleteEndpoints,
UpdateFunc: ew.updateEndpoints,
})
if err != nil {
return nil, err
}
}
return ew, nil
}
////////////////////////
/// EndpointsWatcher ///
////////////////////////
// Subscribe to an authority.
// The provided listener will be updated each time the address set for the
// given authority is changed.
func (ew *EndpointsWatcher) Subscribe(id ServiceID, port Port, hostname string, listener EndpointUpdateListener) error {
svc, _ := ew.k8sAPI.Svc().Lister().Services(id.Namespace).Get(id.Name)
if svc != nil && svc.Spec.Type == corev1.ServiceTypeExternalName {
return invalidService(id.String())
}
if hostname == "" {
ew.log.Debugf("Establishing watch on endpoint [%s:%d]", id, port)
} else {
ew.log.Debugf("Establishing watch on endpoint [%s.%s:%d]", hostname, id, port)
}
sp := ew.getOrNewServicePublisher(id)
sp.subscribe(port, hostname, listener)
return nil
}
// Unsubscribe removes a listener from the subscribers list for this authority.
func (ew *EndpointsWatcher) Unsubscribe(id ServiceID, port Port, hostname string, listener EndpointUpdateListener) {
if hostname == "" {
ew.log.Debugf("Stopping watch on endpoint [%s:%d]", id, port)
} else {
ew.log.Debugf("Stopping watch on endpoint [%s.%s:%d]", hostname, id, port)
}
sp, ok := ew.getServicePublisher(id)
if !ok {
ew.log.Errorf("Cannot unsubscribe from unknown service [%s:%d]", id, port)
return
}
sp.unsubscribe(port, hostname, listener)
}
// removeHandlers will de-register any event handlers used by the
// EndpointsWatcher's informers.
func (ew *EndpointsWatcher) removeHandlers() {
ew.Lock()
defer ew.Unlock()
if ew.svcHandle != nil {
if err := ew.k8sAPI.Svc().Informer().RemoveEventHandler(ew.svcHandle); err != nil {
ew.log.Errorf("Failed to remove Service informer event handlers: %s", err)
}
}
if ew.srvHandle != nil {
if err := ew.k8sAPI.Srv().Informer().RemoveEventHandler(ew.srvHandle); err != nil {
ew.log.Errorf("Failed to remove Server informer event handlers: %s", err)
}
}
if ew.epHandle != nil {
if ew.enableEndpointSlices {
if err := ew.k8sAPI.ES().Informer().RemoveEventHandler(ew.epHandle); err != nil {
ew.log.Errorf("Failed to remove EndpointSlice informer event handlers: %s", err)
}
} else {
if err := ew.k8sAPI.Endpoint().Informer().RemoveEventHandler(ew.epHandle); err != nil {
ew.log.Errorf("Failed to remove Endpoints informer event handlers: %s", err)
}
}
}
}
func (ew *EndpointsWatcher) addService(obj interface{}) {
service := obj.(*corev1.Service)
id := ServiceID{
Namespace: service.Namespace,
Name: service.Name,
}
sp := ew.getOrNewServicePublisher(id)
sp.updateService(service)
}
func (ew *EndpointsWatcher) updateService(oldObj interface{}, newObj interface{}) {
oldService := oldObj.(*corev1.Service)
newService := newObj.(*corev1.Service)
oldUpdated := latestUpdated(oldService.ManagedFields)
updated := latestUpdated(newService.ManagedFields)
if !updated.IsZero() && updated != oldUpdated {
delta := time.Since(updated)
serviceInformerLag.Observe(delta.Seconds())
}
ew.addService(newObj)
}
func (ew *EndpointsWatcher) deleteService(obj interface{}) {
service, ok := obj.(*corev1.Service)
if !ok {
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
if !ok {
ew.log.Errorf("couldn't get object from DeletedFinalStateUnknown %#v", obj)
return
}
service, ok = tombstone.Obj.(*corev1.Service)
if !ok {
ew.log.Errorf("DeletedFinalStateUnknown contained object that is not a Service %#v", obj)
return
}
}
id := ServiceID{
Namespace: service.Namespace,
Name: service.Name,
}
sp, ok := ew.getServicePublisher(id)
if ok {
sp.deleteEndpoints()
}
}
func (ew *EndpointsWatcher) addEndpoints(obj interface{}) {
endpoints, ok := obj.(*corev1.Endpoints)
if !ok {
ew.log.Errorf("error processing endpoints resource, got %#v expected *corev1.Endpoints", obj)
return
}
id := ServiceID{Namespace: endpoints.Namespace, Name: endpoints.Name}
sp := ew.getOrNewServicePublisher(id)
sp.updateEndpoints(endpoints)
}
func (ew *EndpointsWatcher) updateEndpoints(oldObj interface{}, newObj interface{}) {
oldEndpoints, ok := oldObj.(*corev1.Endpoints)
if !ok {
ew.log.Errorf("error processing endpoints resource, got %#v expected *corev1.Endpoints", oldObj)
return
}
newEndpoints, ok := newObj.(*corev1.Endpoints)
if !ok {
ew.log.Errorf("error processing endpoints resource, got %#v expected *corev1.Endpoints", newObj)
return
}
oldUpdated := latestUpdated(oldEndpoints.ManagedFields)
updated := latestUpdated(newEndpoints.ManagedFields)
if !updated.IsZero() && updated != oldUpdated {
delta := time.Since(updated)
endpointsInformerLag.Observe(delta.Seconds())
}
id := ServiceID{Namespace: newEndpoints.Namespace, Name: newEndpoints.Name}
sp := ew.getOrNewServicePublisher(id)
sp.updateEndpoints(newEndpoints)
}
func (ew *EndpointsWatcher) deleteEndpoints(obj interface{}) {
endpoints, ok := obj.(*corev1.Endpoints)
if !ok {
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
if !ok {
ew.log.Errorf("couldn't get object from DeletedFinalStateUnknown %#v", obj)
return
}
endpoints, ok = tombstone.Obj.(*corev1.Endpoints)
if !ok {
ew.log.Errorf("DeletedFinalStateUnknown contained object that is not an Endpoints %#v", obj)
return
}
}
id := ServiceID{
Namespace: endpoints.Namespace,
Name: endpoints.Name,
}
sp, ok := ew.getServicePublisher(id)
if ok {
sp.deleteEndpoints()
}
}
func (ew *EndpointsWatcher) addEndpointSlice(obj interface{}) {
newSlice, ok := obj.(*discovery.EndpointSlice)
if !ok {
ew.log.Errorf("error processing EndpointSlice resource, got %#v expected *discovery.EndpointSlice", obj)
return
}
id, err := getEndpointSliceServiceID(newSlice)
if err != nil {
ew.log.Errorf("Could not fetch resource service name:%v", err)
return
}
sp := ew.getOrNewServicePublisher(id)
sp.addEndpointSlice(newSlice)
}
func (ew *EndpointsWatcher) updateEndpointSlice(oldObj interface{}, newObj interface{}) {
oldSlice, ok := oldObj.(*discovery.EndpointSlice)
if !ok {
ew.log.Errorf("error processing EndpointSlice resource, got %#v expected *discovery.EndpointSlice", oldObj)
return
}
newSlice, ok := newObj.(*discovery.EndpointSlice)
if !ok {
ew.log.Errorf("error processing EndpointSlice resource, got %#v expected *discovery.EndpointSlice", newObj)
return
}
oldUpdated := latestUpdated(oldSlice.ManagedFields)
updated := latestUpdated(newSlice.ManagedFields)
if !updated.IsZero() && updated != oldUpdated {
delta := time.Since(updated)
endpointsliceInformerLag.Observe(delta.Seconds())
}
id, err := getEndpointSliceServiceID(newSlice)
if err != nil {
ew.log.Errorf("Could not fetch resource service name:%v", err)
return
}
sp, ok := ew.getServicePublisher(id)
if ok {
sp.updateEndpointSlice(oldSlice, newSlice)
}
}
func (ew *EndpointsWatcher) deleteEndpointSlice(obj interface{}) {
es, ok := obj.(*discovery.EndpointSlice)
if !ok {
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
if !ok {
ew.log.Errorf("couldn't get object from DeletedFinalStateUnknown %#v", obj)
}
es, ok = tombstone.Obj.(*discovery.EndpointSlice)
if !ok {
ew.log.Errorf("DeletedFinalStateUnknown contained object that is not an EndpointSlice %#v", obj)
return
}
}
id, err := getEndpointSliceServiceID(es)
if err != nil {
ew.log.Errorf("Could not fetch resource service name:%v", err)
}
sp, ok := ew.getServicePublisher(id)
if ok {
sp.deleteEndpointSlice(es)
}
}
// Returns the servicePublisher for the given id if it exists. Otherwise,
// create a new one and return it.
func (ew *EndpointsWatcher) getOrNewServicePublisher(id ServiceID) *servicePublisher {
ew.Lock()
defer ew.Unlock()
// If the service doesn't yet exist, create a stub for it so the listener can
// be registered.
sp, ok := ew.publishers[id]
if !ok {
sp = &servicePublisher{
id: id,
log: ew.log.WithFields(logging.Fields{
"component": "service-publisher",
"ns": id.Namespace,
"svc": id.Name,
}),
k8sAPI: ew.k8sAPI,
metadataAPI: ew.metadataAPI,
cluster: ew.cluster,
ports: make(map[portAndHostname]*portPublisher),
enableEndpointSlices: ew.enableEndpointSlices,
}
ew.publishers[id] = sp
}
return sp
}
func (ew *EndpointsWatcher) getServicePublisher(id ServiceID) (sp *servicePublisher, ok bool) {
ew.RLock()
defer ew.RUnlock()
sp, ok = ew.publishers[id]
return
}
func (ew *EndpointsWatcher) addServer(obj interface{}) {
ew.Lock()
defer ew.Unlock()
server := obj.(*v1beta2.Server)
for _, sp := range ew.publishers {
sp.updateServer(nil, server)
}
}
func (ew *EndpointsWatcher) updateServer(oldObj interface{}, newObj interface{}) {
ew.Lock()
defer ew.Unlock()
oldServer := oldObj.(*v1beta2.Server)
newServer := newObj.(*v1beta2.Server)
if oldServer != nil && newServer != nil {
oldUpdated := latestUpdated(oldServer.ManagedFields)
updated := latestUpdated(newServer.ManagedFields)
if !updated.IsZero() && updated != oldUpdated {
delta := time.Since(updated)
serverInformerLag.Observe(delta.Seconds())
}
}
namespace := ""
if oldServer != nil {
namespace = oldServer.GetNamespace()
}
if newServer != nil {
namespace = newServer.GetNamespace()
}
for id, sp := range ew.publishers {
// Servers may only select workloads in their namespace.
if id.Namespace == namespace {
sp.updateServer(oldServer, newServer)
}
}
}
func (ew *EndpointsWatcher) deleteServer(obj interface{}) {
ew.Lock()
defer ew.Unlock()
server := obj.(*v1beta2.Server)
for _, sp := range ew.publishers {
sp.updateServer(server, nil)
}
}
////////////////////////
/// servicePublisher ///
////////////////////////
func (sp *servicePublisher) updateEndpoints(newEndpoints *corev1.Endpoints) {
sp.Lock()
defer sp.Unlock()
sp.log.Debugf("Updating endpoints for %s", sp.id)
for _, port := range sp.ports {
port.updateEndpoints(newEndpoints)
}
}
func (sp *servicePublisher) deleteEndpoints() {
sp.Lock()
defer sp.Unlock()
sp.log.Debugf("Deleting endpoints for %s", sp.id)
for _, port := range sp.ports {
port.noEndpoints(false)
}
}
func (sp *servicePublisher) addEndpointSlice(newSlice *discovery.EndpointSlice) {
sp.Lock()
defer sp.Unlock()
sp.log.Debugf("Adding ES %s/%s", newSlice.Namespace, newSlice.Name)
for _, port := range sp.ports {
port.addEndpointSlice(newSlice)
}
}
func (sp *servicePublisher) updateEndpointSlice(oldSlice *discovery.EndpointSlice, newSlice *discovery.EndpointSlice) {
sp.Lock()
defer sp.Unlock()
sp.log.Debugf("Updating ES %s/%s", oldSlice.Namespace, oldSlice.Name)
for _, port := range sp.ports {
port.updateEndpointSlice(oldSlice, newSlice)
}
}
func (sp *servicePublisher) deleteEndpointSlice(es *discovery.EndpointSlice) {
sp.Lock()
defer sp.Unlock()
sp.log.Debugf("Deleting ES %s/%s", es.Namespace, es.Name)
for _, port := range sp.ports {
port.deleteEndpointSlice(es)
}
}
func (sp *servicePublisher) updateService(newService *corev1.Service) {
sp.Lock()
defer sp.Unlock()
sp.log.Debugf("Updating service for %s", sp.id)
// set localTrafficPolicy to true if InternalTrafficPolicy is set to local
if newService.Spec.InternalTrafficPolicy != nil {
sp.localTrafficPolicy = *newService.Spec.InternalTrafficPolicy == corev1.ServiceInternalTrafficPolicyLocal
} else {
sp.localTrafficPolicy = false
}
for key, port := range sp.ports {
newTargetPort := getTargetPort(newService, key.port)
if newTargetPort != port.targetPort {
port.updatePort(newTargetPort)
}
// update service endpoints with new localTrafficPolicy
if port.localTrafficPolicy != sp.localTrafficPolicy {
port.updateLocalTrafficPolicy(sp.localTrafficPolicy)
}
}
}
func (sp *servicePublisher) subscribe(srcPort Port, hostname string, listener EndpointUpdateListener) {
sp.Lock()
defer sp.Unlock()
key := portAndHostname{
port: srcPort,
hostname: hostname,
}
port, ok := sp.ports[key]
if !ok {
port = sp.newPortPublisher(srcPort, hostname)
sp.ports[key] = port
}
port.subscribe(listener)
}
func (sp *servicePublisher) unsubscribe(srcPort Port, hostname string, listener EndpointUpdateListener) {
sp.Lock()
defer sp.Unlock()
key := portAndHostname{
port: srcPort,
hostname: hostname,
}
port, ok := sp.ports[key]
if ok {
port.unsubscribe(listener)
if len(port.listeners) == 0 {
endpointsVecs.unregister(sp.metricsLabels(srcPort, hostname))
delete(sp.ports, key)
}
}
}
func (sp *servicePublisher) newPortPublisher(srcPort Port, hostname string) *portPublisher {
targetPort := intstr.FromInt(int(srcPort))
svc, err := sp.k8sAPI.Svc().Lister().Services(sp.id.Namespace).Get(sp.id.Name)
if err != nil && !apierrors.IsNotFound(err) {
sp.log.Errorf("error getting service: %s", err)
}
exists := false
if err == nil {
targetPort = getTargetPort(svc, srcPort)
exists = true
}
log := sp.log.WithField("port", srcPort)
port := &portPublisher{
listeners: []EndpointUpdateListener{},
targetPort: targetPort,
srcPort: srcPort,
hostname: hostname,
exists: exists,
k8sAPI: sp.k8sAPI,
metadataAPI: sp.metadataAPI,
log: log,
metrics: endpointsVecs.newEndpointsMetrics(sp.metricsLabels(srcPort, hostname)),
enableEndpointSlices: sp.enableEndpointSlices,
localTrafficPolicy: sp.localTrafficPolicy,
}
if port.enableEndpointSlices {
matchLabels := map[string]string{discovery.LabelServiceName: sp.id.Name}
selector := labels.Set(matchLabels).AsSelector()
sliceList, err := sp.k8sAPI.ES().Lister().EndpointSlices(sp.id.Namespace).List(selector)
if err != nil && !apierrors.IsNotFound(err) {
sp.log.Errorf("error getting endpointSlice list: %s", err)
}
if err == nil {
for _, slice := range sliceList {
port.addEndpointSlice(slice)
}
}
} else {
endpoints, err := sp.k8sAPI.Endpoint().Lister().Endpoints(sp.id.Namespace).Get(sp.id.Name)
if err != nil && !apierrors.IsNotFound(err) {
sp.log.Errorf("error getting endpoints: %s", err)
}
if err == nil {
port.updateEndpoints(endpoints)
}
}
return port
}
func (sp *servicePublisher) metricsLabels(port Port, hostname string) prometheus.Labels {
return endpointsLabels(sp.cluster, sp.id.Namespace, sp.id.Name, strconv.Itoa(int(port)), hostname)
}
func (sp *servicePublisher) updateServer(oldServer, newServer *v1beta2.Server) {
sp.Lock()
defer sp.Unlock()
for _, pp := range sp.ports {
pp.updateServer(oldServer, newServer)
}
}
/////////////////////
/// portPublisher ///
/////////////////////
// Note that portPublishers methods are generally NOT thread-safe. You should
// hold the parent servicePublisher's mutex before calling methods on a
// portPublisher.
func (pp *portPublisher) updateEndpoints(endpoints *corev1.Endpoints) {
newAddressSet := pp.endpointsToAddresses(endpoints)
if len(newAddressSet.Addresses) == 0 {
for _, listener := range pp.listeners {
listener.NoEndpoints(true)
}
} else {
add, remove := diffAddresses(pp.addresses, newAddressSet)
for _, listener := range pp.listeners {
if len(remove.Addresses) > 0 {
listener.Remove(remove)
}
if len(add.Addresses) > 0 {
listener.Add(add)
}
}
}
pp.addresses = newAddressSet
pp.exists = true
pp.metrics.incUpdates()
pp.metrics.setPods(len(pp.addresses.Addresses))
pp.metrics.setExists(true)
}
func (pp *portPublisher) addEndpointSlice(slice *discovery.EndpointSlice) {
newAddressSet := pp.endpointSliceToAddresses(slice)
for id, addr := range pp.addresses.Addresses {
if _, ok := newAddressSet.Addresses[id]; !ok {
newAddressSet.Addresses[id] = addr
}
}
add, _ := diffAddresses(pp.addresses, newAddressSet)
if len(add.Addresses) > 0 {
for _, listener := range pp.listeners {
listener.Add(add)
}
}
// even if the ES doesn't have addresses yet we need to create a new
// pp.addresses entry with the appropriate Labels and LocalTrafficPolicy,
// which isn't going to be captured during the ES update event when
// addresses get added
pp.addresses = newAddressSet
pp.exists = true
pp.metrics.incUpdates()
pp.metrics.setPods(len(pp.addresses.Addresses))
pp.metrics.setExists(true)
}
func (pp *portPublisher) updateEndpointSlice(oldSlice *discovery.EndpointSlice, newSlice *discovery.EndpointSlice) {
updatedAddressSet := AddressSet{
Addresses: make(map[ID]Address),
Labels: pp.addresses.Labels,
LocalTrafficPolicy: pp.localTrafficPolicy,
}
for id, address := range pp.addresses.Addresses {
updatedAddressSet.Addresses[id] = address
}
for _, id := range pp.endpointSliceToIDs(oldSlice) {
delete(updatedAddressSet.Addresses, id)
}
newAddressSet := pp.endpointSliceToAddresses(newSlice)
for id, address := range newAddressSet.Addresses {
updatedAddressSet.Addresses[id] = address
}
add, remove := diffAddresses(pp.addresses, updatedAddressSet)
for _, listener := range pp.listeners {
if len(remove.Addresses) > 0 {
listener.Remove(remove)
}
if len(add.Addresses) > 0 {
listener.Add(add)
}
}
pp.addresses = updatedAddressSet
pp.exists = true
pp.metrics.incUpdates()
pp.metrics.setPods(len(pp.addresses.Addresses))
pp.metrics.setExists(true)
}
func metricLabels(resource interface{}) map[string]string {
var serviceName, ns string
var resLabels, resAnnotations map[string]string
switch res := resource.(type) {
case *corev1.Endpoints:
{
serviceName, ns = res.Name, res.Namespace
resLabels, resAnnotations = res.Labels, res.Annotations
}
case *discovery.EndpointSlice:
{
serviceName, ns = res.Labels[discovery.LabelServiceName], res.Namespace
resLabels, resAnnotations = res.Labels, res.Annotations
}
}
labels := map[string]string{service: serviceName, namespace: ns}
remoteClusterName, hasRemoteClusterName := resLabels[consts.RemoteClusterNameLabel]
serviceFqn, hasServiceFqn := resAnnotations[consts.RemoteServiceFqName]
if hasRemoteClusterName {
// this means we are looking at Endpoints created for the purpose of mirroring
// an out of cluster service.
labels[targetCluster] = remoteClusterName
if hasServiceFqn {
fqParts := strings.Split(serviceFqn, ".")
if len(fqParts) >= 2 {
labels[targetService] = fqParts[0]
labels[targetServiceNamespace] = fqParts[1]
}
}
}
return labels
}
func (pp *portPublisher) endpointSliceToAddresses(es *discovery.EndpointSlice) AddressSet {
resolvedPort := pp.resolveESTargetPort(es.Ports)
if resolvedPort == undefinedEndpointPort {
return AddressSet{
Labels: metricLabels(es),
Addresses: make(map[ID]Address),
LocalTrafficPolicy: pp.localTrafficPolicy,
}
}
serviceID, err := getEndpointSliceServiceID(es)
if err != nil {
pp.log.Errorf("Could not fetch resource service name:%v", err)
}
addresses := make(map[ID]Address)
for _, endpoint := range es.Endpoints {
if endpoint.Hostname != nil {
if pp.hostname != "" && pp.hostname != *endpoint.Hostname {
continue
}
}
if endpoint.Conditions.Ready != nil && !*endpoint.Conditions.Ready {
continue
}
if endpoint.TargetRef == nil {
for _, IPAddr := range endpoint.Addresses {
var authorityOverride string
if fqName, ok := es.Annotations[consts.RemoteServiceFqName]; ok {
authorityOverride = net.JoinHostPort(fqName, fmt.Sprintf("%d", pp.srcPort))
}
identity := es.Annotations[consts.RemoteGatewayIdentity]
address, id := pp.newServiceRefAddress(resolvedPort, IPAddr, serviceID.Name, es.Namespace)
address.Identity, address.AuthorityOverride = identity, authorityOverride
if endpoint.Hints != nil {
zones := make([]discovery.ForZone, len(endpoint.Hints.ForZones))
copy(zones, endpoint.Hints.ForZones)
address.ForZones = zones
}
addresses[id] = address
}
continue
}
if endpoint.TargetRef.Kind == endpointTargetRefPod {
for _, IPAddr := range endpoint.Addresses {
address, id, err := pp.newPodRefAddress(
resolvedPort,
es.AddressType,
IPAddr,
endpoint.TargetRef.Name,
endpoint.TargetRef.Namespace,
)
if err != nil {
pp.log.Errorf("Unable to create new address:%v", err)
continue
}
err = SetToServerProtocol(pp.k8sAPI, &address)
if err != nil {
pp.log.Errorf("failed to set address OpaqueProtocol: %s", err)
continue
}
address.Zone = endpoint.Zone
if endpoint.Hints != nil {
zones := make([]discovery.ForZone, len(endpoint.Hints.ForZones))
copy(zones, endpoint.Hints.ForZones)
address.ForZones = zones
}
addresses[id] = address
}
}
if endpoint.TargetRef.Kind == endpointTargetRefExternalWorkload {
for _, IPAddr := range endpoint.Addresses {
address, id, err := pp.newExtRefAddress(resolvedPort, IPAddr, endpoint.TargetRef.Name, es.Namespace)
if err != nil {
pp.log.Errorf("Unable to create new address: %v", err)
continue
}
err = SetToServerProtocolExternalWorkload(pp.k8sAPI, &address)
if err != nil {
pp.log.Errorf("failed to set address OpaqueProtocol: %s", err)
continue
}
address.Zone = endpoint.Zone
if endpoint.Hints != nil {
zones := make([]discovery.ForZone, len(endpoint.Hints.ForZones))
copy(zones, endpoint.Hints.ForZones)
address.ForZones = zones
}
addresses[id] = address
}
}
}
return AddressSet{
Addresses: addresses,
Labels: metricLabels(es),
LocalTrafficPolicy: pp.localTrafficPolicy,
}
}
// endpointSliceToIDs is similar to endpointSliceToAddresses but instead returns
// only the IDs of the endpoints rather than the addresses themselves.
func (pp *portPublisher) endpointSliceToIDs(es *discovery.EndpointSlice) []ID {
resolvedPort := pp.resolveESTargetPort(es.Ports)
if resolvedPort == undefinedEndpointPort {
return []ID{}
}
serviceID, err := getEndpointSliceServiceID(es)
if err != nil {
pp.log.Errorf("Could not fetch resource service name:%v", err)
}
ids := []ID{}
for _, endpoint := range es.Endpoints {
if endpoint.Hostname != nil {
if pp.hostname != "" && pp.hostname != *endpoint.Hostname {
continue
}
}
if endpoint.Conditions.Ready != nil && !*endpoint.Conditions.Ready {
continue
}
if endpoint.TargetRef == nil {
for _, IPAddr := range endpoint.Addresses {
ids = append(ids, ServiceID{
Name: strings.Join([]string{
serviceID.Name,
IPAddr,
fmt.Sprint(resolvedPort),
}, "-"),
Namespace: es.Namespace,
})
}
continue
}
if endpoint.TargetRef.Kind == endpointTargetRefPod {
ids = append(ids, PodID{
Name: endpoint.TargetRef.Name,
Namespace: endpoint.TargetRef.Namespace,
IPFamily: corev1.IPFamily(es.AddressType),
})
} else if endpoint.TargetRef.Kind == endpointTargetRefExternalWorkload {
ids = append(ids, ExternalWorkloadID{
Name: endpoint.TargetRef.Name,
Namespace: endpoint.TargetRef.Namespace,
})
}
}
return ids
}
func (pp *portPublisher) endpointsToAddresses(endpoints *corev1.Endpoints) AddressSet {
addresses := make(map[ID]Address)
for _, subset := range endpoints.Subsets {
resolvedPort := pp.resolveTargetPort(subset)
if resolvedPort == undefinedEndpointPort {
continue
}
for _, endpoint := range subset.Addresses {
if pp.hostname != "" && pp.hostname != endpoint.Hostname {
continue
}
if endpoint.TargetRef == nil {
var authorityOverride string
if fqName, ok := endpoints.Annotations[consts.RemoteServiceFqName]; ok {
authorityOverride = fmt.Sprintf("%s:%d", fqName, pp.srcPort)
}
identity := endpoints.Annotations[consts.RemoteGatewayIdentity]
address, id := pp.newServiceRefAddress(resolvedPort, endpoint.IP, endpoints.Name, endpoints.Namespace)
address.Identity, address.AuthorityOverride = identity, authorityOverride
addresses[id] = address
continue
}
if endpoint.TargetRef.Kind == endpointTargetRefPod {
address, id, err := pp.newPodRefAddress(
resolvedPort,
"",
endpoint.IP,
endpoint.TargetRef.Name,
endpoint.TargetRef.Namespace,
)
if err != nil {
pp.log.Errorf("Unable to create new address:%v", err)
continue
}
err = SetToServerProtocol(pp.k8sAPI, &address)
if err != nil {
pp.log.Errorf("failed to set address OpaqueProtocol: %s", err)
continue
}
addresses[id] = address
}
}
}
return AddressSet{
Addresses: addresses,
Labels: metricLabels(endpoints),
}
}
func (pp *portPublisher) newServiceRefAddress(endpointPort Port, endpointIP, serviceName, serviceNamespace string) (Address, ServiceID) {
id := ServiceID{
Name: strings.Join([]string{
serviceName,
endpointIP,
fmt.Sprint(endpointPort),
}, "-"),
Namespace: serviceNamespace,
}
return Address{IP: endpointIP, Port: endpointPort}, id
}
func (pp *portPublisher) newPodRefAddress(
endpointPort Port,
ipFamily discovery.AddressType,
endpointIP,
podName,
podNamespace string,
) (Address, PodID, error) {
id := PodID{
Name: podName,
Namespace: podNamespace,
IPFamily: corev1.IPFamily(ipFamily),
}
pod, err := pp.k8sAPI.Pod().Lister().Pods(id.Namespace).Get(id.Name)
if err != nil {
return Address{}, PodID{}, fmt.Errorf("unable to fetch pod %v: %w", id, err)
}
ownerKind, ownerName, err := pp.metadataAPI.GetOwnerKindAndName(context.Background(), pod, false)
if err != nil {
return Address{}, PodID{}, err
}
addr := Address{
IP: endpointIP,
Port: endpointPort,
Pod: pod,
OwnerName: ownerName,
OwnerKind: ownerKind,
}
return addr, id, nil
}
func (pp *portPublisher) newExtRefAddress(endpointPort Port, endpointIP, externalWorkloadName, externalWorkloadNamespace string) (Address, ExternalWorkloadID, error) {
id := ExternalWorkloadID{
Name: externalWorkloadName,
Namespace: externalWorkloadNamespace,
}
ew, err := pp.k8sAPI.ExtWorkload().Lister().ExternalWorkloads(id.Namespace).Get(id.Name)
if err != nil {
return Address{}, ExternalWorkloadID{}, fmt.Errorf("unable to fetch ExternalWorkload %v: %w", id, err)
}
addr := Address{
IP: endpointIP,
Port: endpointPort,
ExternalWorkload: ew,
}
ownerRefs := ew.GetOwnerReferences()
if len(ownerRefs) == 1 {
parent := ownerRefs[0]
addr.OwnerName = parent.Name
addr.OwnerName = strings.ToLower(parent.Kind)
}
return addr, id, nil
}
func (pp *portPublisher) resolveESTargetPort(slicePorts []discovery.EndpointPort) Port {
if slicePorts == nil {
return undefinedEndpointPort
}
switch pp.targetPort.Type {
case intstr.Int:
return Port(pp.targetPort.IntVal)
case intstr.String:
for _, p := range slicePorts {
name := ""
if p.Name != nil {
name = *p.Name
}
if name == pp.targetPort.StrVal {
return Port(*p.Port)
}
}
}
return undefinedEndpointPort
}
func (pp *portPublisher) resolveTargetPort(subset corev1.EndpointSubset) Port {
switch pp.targetPort.Type {
case intstr.Int:
return Port(pp.targetPort.IntVal)
case intstr.String:
for _, p := range subset.Ports {
if p.Name == pp.targetPort.StrVal {
return Port(p.Port)
}
}
}
return undefinedEndpointPort
}
func (pp *portPublisher) updateLocalTrafficPolicy(localTrafficPolicy bool) {
pp.localTrafficPolicy = localTrafficPolicy
pp.addresses.LocalTrafficPolicy = localTrafficPolicy
for _, listener := range pp.listeners {
listener.Add(pp.addresses.shallowCopy())
}
}
func (pp *portPublisher) updatePort(targetPort namedPort) {
pp.targetPort = targetPort
if pp.enableEndpointSlices {
matchLabels := map[string]string{discovery.LabelServiceName: pp.id.Name}
selector := labels.Set(matchLabels).AsSelector()
endpointSlices, err := pp.k8sAPI.ES().Lister().EndpointSlices(pp.id.Namespace).List(selector)
if err == nil {
pp.addresses = AddressSet{}
for _, slice := range endpointSlices {
pp.addEndpointSlice(slice)
}
} else {
pp.log.Errorf("Unable to get EndpointSlices during port update: %s", err)
}
} else {
endpoints, err := pp.k8sAPI.Endpoint().Lister().Endpoints(pp.id.Namespace).Get(pp.id.Name)
if err == nil {
pp.updateEndpoints(endpoints)
} else {
pp.log.Errorf("Unable to get endpoints during port update: %s", err)
}
}
}
func (pp *portPublisher) deleteEndpointSlice(es *discovery.EndpointSlice) {
addrSet := pp.endpointSliceToAddresses(es)
for id := range addrSet.Addresses {
delete(pp.addresses.Addresses, id)
}
for _, listener := range pp.listeners {
listener.Remove(addrSet)
}
if len(pp.addresses.Addresses) == 0 {
pp.noEndpoints(false)
} else {
pp.exists = true
pp.metrics.incUpdates()
pp.metrics.setPods(len(pp.addresses.Addresses))
pp.metrics.setExists(true)
}
}
func (pp *portPublisher) noEndpoints(exists bool) {
pp.exists = exists
pp.addresses = AddressSet{}
for _, listener := range pp.listeners {
listener.NoEndpoints(exists)
}
pp.metrics.incUpdates()
pp.metrics.setExists(exists)
pp.metrics.setPods(0)
}
func (pp *portPublisher) subscribe(listener EndpointUpdateListener) {
if pp.exists {
if len(pp.addresses.Addresses) > 0 {
listener.Add(pp.addresses.shallowCopy())
} else {
listener.NoEndpoints(true)
}
} else {
listener.NoEndpoints(false)
}
pp.listeners = append(pp.listeners, listener)
pp.metrics.setSubscribers(len(pp.listeners))
}
func (pp *portPublisher) unsubscribe(listener EndpointUpdateListener) {
for i, e := range pp.listeners {
if e == listener {
n := len(pp.listeners)
pp.listeners[i] = pp.listeners[n-1]
pp.listeners[n-1] = nil
pp.listeners = pp.listeners[:n-1]
break
}
}
pp.metrics.setSubscribers(len(pp.listeners))
}
func (pp *portPublisher) updateServer(oldServer, newServer *v1beta2.Server) {
updated := false
for id, address := range pp.addresses.Addresses {
if pp.isAddressSelected(address, oldServer) || pp.isAddressSelected(address, newServer) {
if newServer != nil && pp.isAddressSelected(address, newServer) && newServer.Spec.ProxyProtocol == opaqueProtocol {
address.OpaqueProtocol = true
} else {
address.OpaqueProtocol = false
}
if pp.addresses.Addresses[id].OpaqueProtocol != address.OpaqueProtocol {
pp.addresses.Addresses[id] = address
updated = true
}
}
}
if updated {
for _, listener := range pp.listeners {
listener.Add(pp.addresses.shallowCopy())
}
pp.metrics.incUpdates()
}
}
func (pp *portPublisher) isAddressSelected(address Address, server *v1beta2.Server) bool {
if server == nil {
return false
}
if address.Pod != nil {
selector, err := metav1.LabelSelectorAsSelector(server.Spec.PodSelector)
if err != nil {
pp.log.Errorf("failed to create Selector: %s", err)
return false
}
if !selector.Matches(labels.Set(address.Pod.Labels)) {
return false
}
switch server.Spec.Port.Type {
case intstr.Int:
if server.Spec.Port.IntVal == int32(address.Port) {
return true
}
case intstr.String:
for _, c := range address.Pod.Spec.Containers {
for _, p := range c.Ports {
if p.ContainerPort == int32(address.Port) && p.Name == server.Spec.Port.StrVal {
return true
}
}
}
}
} else if address.ExternalWorkload != nil {
selector, err := metav1.LabelSelectorAsSelector(server.Spec.ExternalWorkloadSelector)
if err != nil {
pp.log.Errorf("failed to create Selector: %s", err)
return false
}
if !selector.Matches(labels.Set(address.ExternalWorkload.Labels)) {
return false
}
switch server.Spec.Port.Type {
case intstr.Int:
if server.Spec.Port.IntVal == int32(address.Port) {
return true
}
case intstr.String:
for _, p := range address.ExternalWorkload.Spec.Ports {
if p.Port == int32(address.Port) && p.Name == server.Spec.Port.StrVal {
return true
}
}
}
}
return false
}
////////////
/// util ///
////////////
// getTargetPort returns the port specified as an argument if no service is
// present. If the service is present and it has a port spec matching the
// specified port, it returns the name of the service's port (not the name
// of the target pod port), so that it can be looked up in the endpoints API
// response, which uses service port names.
func getTargetPort(service *corev1.Service, port Port) namedPort {
// Use the specified port as the target port by default
targetPort := intstr.FromInt(int(port))
if service == nil {
return targetPort
}
// If a port spec exists with a port matching the specified port use that
// port spec's name as the target port
for _, portSpec := range service.Spec.Ports {
if portSpec.Port == int32(port) {
return intstr.FromString(portSpec.Name)
}
}
return targetPort
}
func addressChanged(oldAddress Address, newAddress Address) bool {
if oldAddress.Identity != newAddress.Identity {
// in this case the identity could have changed; this can happen when for
// example a mirrored service is reassigned to a new gateway with a different
// identity and the service mirroring controller picks that and updates the
// identity
return true
}
// If the zone hints have changed, then the address has changed
if len(newAddress.ForZones) != len(oldAddress.ForZones) {
return true
}
// Sort the zone information so that we can compare them accurately
// We can't use `sort.StringSlice` because these are arrays of structs and not just strings
sort.Slice(oldAddress.ForZones, func(i, j int) bool {
return oldAddress.ForZones[i].Name < (oldAddress.ForZones[j].Name)
})
sort.Slice(newAddress.ForZones, func(i, j int) bool {
return newAddress.ForZones[i].Name < (newAddress.ForZones[j].Name)
})
// Both old and new addresses have the same number of zones, so we can just compare them directly
for k := range oldAddress.ForZones {
if oldAddress.ForZones[k].Name != newAddress.ForZones[k].Name {
return true
}
}
if oldAddress.Pod != nil && newAddress.Pod != nil {
// if these addresses are owned by pods we can check the resource versions
return oldAddress.Pod.ResourceVersion != newAddress.Pod.ResourceVersion
}
return false
}
func diffAddresses(oldAddresses, newAddresses AddressSet) (add, remove AddressSet) {
// TODO: this detects pods which have been added or removed, but does not
// detect addresses which have been modified. A modified address should trigger
// an add of the new version.
addAddresses := make(map[ID]Address)
removeAddresses := make(map[ID]Address)
for id, newAddress := range newAddresses.Addresses {
if oldAddress, ok := oldAddresses.Addresses[id]; ok {
if addressChanged(oldAddress, newAddress) {
addAddresses[id] = newAddress
}
} else {
// this is a new address, we need to add it
addAddresses[id] = newAddress
}
}
for id, address := range oldAddresses.Addresses {
if _, ok := newAddresses.Addresses[id]; !ok {
removeAddresses[id] = address
}
}
add = AddressSet{
Addresses: addAddresses,
Labels: newAddresses.Labels,
LocalTrafficPolicy: newAddresses.LocalTrafficPolicy,
}
remove = AddressSet{
Addresses: removeAddresses,
}
return add, remove
}
func getEndpointSliceServiceID(es *discovery.EndpointSlice) (ServiceID, error) {
if !isValidSlice(es) {
return ServiceID{}, fmt.Errorf("EndpointSlice [%s/%s] is invalid", es.Namespace, es.Name)
}
if svc, ok := es.Labels[discovery.LabelServiceName]; ok {
return ServiceID{Namespace: es.Namespace, Name: svc}, nil
}
for _, ref := range es.OwnerReferences {
if ref.Kind == "Service" && ref.Name != "" {
return ServiceID{Namespace: es.Namespace, Name: ref.Name}, nil
}
}
return ServiceID{}, fmt.Errorf("EndpointSlice [%s/%s] is invalid", es.Namespace, es.Name)
}
func isValidSlice(es *discovery.EndpointSlice) bool {
serviceName, ok := es.Labels[discovery.LabelServiceName]
if !ok && len(es.OwnerReferences) == 0 {
return false
} else if len(es.OwnerReferences) == 0 && serviceName == "" {
return false
}
return true
}
// SetToServerProtocol sets the address's OpaqueProtocol field based off any
// Servers that select it and override the expected protocol.
func SetToServerProtocol(k8sAPI *k8s.API, address *Address) error {
if address.Pod == nil {
return fmt.Errorf("endpoint not backed by Pod: %s:%d", address.IP, address.Port)
}
servers, err := k8sAPI.Srv().Lister().Servers("").List(labels.Everything())
if err != nil {
return fmt.Errorf("failed to list Servers: %w", err)
}
for _, server := range servers {
selector, err := metav1.LabelSelectorAsSelector(server.Spec.PodSelector)
if err != nil {
return fmt.Errorf("failed to create Selector: %w", err)
}
if server.Spec.ProxyProtocol == opaqueProtocol && selector.Matches(labels.Set(address.Pod.Labels)) {
var portMatch bool
switch server.Spec.Port.Type {
case intstr.Int:
if server.Spec.Port.IntVal == int32(address.Port) {
portMatch = true
}
case intstr.String:
for _, c := range address.Pod.Spec.Containers {
for _, p := range c.Ports {
if (p.ContainerPort == int32(address.Port) || p.HostPort == int32(address.Port)) &&
p.Name == server.Spec.Port.StrVal {
portMatch = true
}
}
}
default:
continue
}
if portMatch {
address.OpaqueProtocol = true
return nil
}
}
}
return nil
}
// setToServerProtocolExternalWorkload sets the address's OpaqueProtocol field based off any
// Servers that select it and override the expected protocol for ExternalWorkloads.
func SetToServerProtocolExternalWorkload(k8sAPI *k8s.API, address *Address) error {
if address.ExternalWorkload == nil {
return fmt.Errorf("endpoint not backed by ExternalWorkload: %s:%d", address.IP, address.Port)
}
servers, err := k8sAPI.Srv().Lister().Servers("").List(labels.Everything())
if err != nil {
return fmt.Errorf("failed to list Servers: %w", err)
}
for _, server := range servers {
selector, err := metav1.LabelSelectorAsSelector(server.Spec.ExternalWorkloadSelector)
if err != nil {
return fmt.Errorf("failed to create Selector: %w", err)
}
if server.Spec.ProxyProtocol == opaqueProtocol && selector.Matches(labels.Set(address.ExternalWorkload.Labels)) {
var portMatch bool
switch server.Spec.Port.Type {
case intstr.Int:
if server.Spec.Port.IntVal == int32(address.Port) {
portMatch = true
}
case intstr.String:
for _, p := range address.ExternalWorkload.Spec.Ports {
if p.Port == int32(address.Port) && p.Name == server.Spec.Port.StrVal {
portMatch = true
}
}
default:
continue
}
if portMatch {
address.OpaqueProtocol = true
return nil
}
}
}
return nil
}
func latestUpdated(managedFields []metav1.ManagedFieldsEntry) time.Time {
var latest time.Time
for _, field := range managedFields {
if field.Operation == metav1.ManagedFieldsOperationUpdate {
if latest.IsZero() || field.Time.After(latest) {
latest = field.Time.Time
}
}
}
return latest
}
package watcher
import (
"errors"
"fmt"
"net"
ext "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1"
"github.com/linkerd/linkerd2/controller/k8s"
"github.com/prometheus/client_golang/prometheus"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/tools/cache"
)
const (
// PodIPIndex is the key for the index based on Pod IPs
PodIPIndex = "ip"
// HostIPIndex is the key for the index based on Host IP of pods with host network enabled
HostIPIndex = "hostIP"
// ExternalWorkloadIPIndex is the key for the index based on IP of externalworkloads
ExternalWorkloadIPIndex = "externalWorkloadIP"
)
type (
// IPPort holds the IP and port for some destination
IPPort struct {
IP string
Port Port
}
// ID is a namespace-qualified name.
ID struct {
Namespace string
Name string
// Only used for PodID
IPFamily corev1.IPFamily
}
// ServiceID is the namespace-qualified name of a service.
ServiceID = ID
// PodID is the namespace-qualified name of a pod.
PodID = ID
// ProfileID is the namespace-qualified name of a service profile.
ProfileID = ID
// PodID is the namespace-qualified name of an ExternalWorkload.
ExternalWorkloadID = ID
// Port is a numeric port.
Port = uint32
namedPort = intstr.IntOrString
// InvalidService is an error which indicates that the authority is not a
// valid service.
InvalidService struct {
authority string
}
)
// Labels returns the labels for prometheus metrics associated to the service
func (id ServiceID) Labels() prometheus.Labels {
return prometheus.Labels{"namespace": id.Namespace, "name": id.Name}
}
func (is InvalidService) Error() string {
return fmt.Sprintf("Invalid k8s service %s", is.authority)
}
func invalidService(authority string) InvalidService {
return InvalidService{authority}
}
func (i ID) String() string {
return fmt.Sprintf("%s/%s", i.Namespace, i.Name)
}
// InitializeIndexers is used to initialize indexers on k8s informers, to be used across watchers
func InitializeIndexers(k8sAPI *k8s.API) error {
err := k8sAPI.Svc().Informer().AddIndexers(cache.Indexers{PodIPIndex: func(obj interface{}) ([]string, error) {
svc, ok := obj.(*corev1.Service)
if !ok {
return nil, errors.New("object is not a service")
}
if len(svc.Spec.ClusterIPs) != 0 {
return svc.Spec.ClusterIPs, nil
}
if svc.Spec.ClusterIP != "" {
return []string{svc.Spec.ClusterIP}, nil
}
return nil, nil
}})
if err != nil {
return fmt.Errorf("could not create an indexer for services: %w", err)
}
err = k8sAPI.Pod().Informer().AddIndexers(cache.Indexers{PodIPIndex: func(obj interface{}) ([]string, error) {
if pod, ok := obj.(*corev1.Pod); ok {
// Pods that run in the host network are indexed by the host IP
// indexer in the IP watcher; they should be skipped by the pod
// IP indexer which is responsible only for indexing pod network
// pods.
if pod.Spec.HostNetwork {
return nil, nil
}
ips := []string{}
for _, pip := range pod.Status.PodIPs {
if pip.IP != "" {
ips = append(ips, pip.IP)
}
}
if len(ips) == 0 && pod.Status.PodIP != "" {
ips = append(ips, pod.Status.PodIP)
}
return ips, nil
}
return nil, fmt.Errorf("object is not a pod")
}})
if err != nil {
return fmt.Errorf("could not create an indexer for pods: %w", err)
}
err = k8sAPI.Pod().Informer().AddIndexers(cache.Indexers{HostIPIndex: func(obj interface{}) ([]string, error) {
pod, ok := obj.(*corev1.Pod)
if !ok {
return nil, errors.New("object is not a pod")
}
ips := []string{}
for _, hip := range pod.Status.HostIPs {
ips = append(ips, hip.IP)
}
if len(ips) == 0 && pod.Status.HostIP != "" {
ips = append(ips, pod.Status.HostIP)
}
if len(ips) == 0 {
return []string{}, nil
}
// If the pod is reachable from the host network, then for
// each of its containers' ports that exposes a host port, add
// that hostIP:hostPort endpoint to the indexer.
addrs := []string{}
for _, c := range pod.Spec.Containers {
for _, p := range c.Ports {
if p.HostPort == 0 {
continue
}
for _, ip := range ips {
addrs = append(addrs, net.JoinHostPort(ip, fmt.Sprintf("%d", p.HostPort)))
}
}
}
return addrs, nil
}})
if err != nil {
return fmt.Errorf("could not create an indexer for pods: %w", err)
}
err = k8sAPI.ExtWorkload().Informer().AddIndexers(cache.Indexers{ExternalWorkloadIPIndex: func(obj interface{}) ([]string, error) {
ew, ok := obj.(*ext.ExternalWorkload)
if !ok {
return nil, errors.New("object is not an externalworkload")
}
addrs := []string{}
for _, ip := range ew.Spec.WorkloadIPs {
for _, port := range ew.Spec.Ports {
addrs = append(addrs, net.JoinHostPort(ip.Ip, fmt.Sprintf("%d", port.Port)))
}
}
return addrs, nil
}})
if err != nil {
return fmt.Errorf("could not create an indexer for externalworkloads: %w", err)
}
return nil
}
func getIndexedPods(k8sAPI *k8s.API, indexName string, key string) ([]*corev1.Pod, error) {
objs, err := k8sAPI.Pod().Informer().GetIndexer().ByIndex(indexName, key)
if err != nil {
return nil, fmt.Errorf("failed getting %s indexed pods: %w", indexName, err)
}
pods := make([]*corev1.Pod, 0)
for _, obj := range objs {
pod := obj.(*corev1.Pod)
if !podNotTerminating(pod) {
continue
}
pods = append(pods, pod)
}
return pods, nil
}
func getIndexedExternalWorkloads(k8sAPI *k8s.API, indexName string, key string) ([]*ext.ExternalWorkload, error) {
objs, err := k8sAPI.ExtWorkload().Informer().GetIndexer().ByIndex(indexName, key)
if err != nil {
return nil, fmt.Errorf("failed getting %s indexed externalworkloads: %w", indexName, err)
}
workloads := make([]*ext.ExternalWorkload, 0)
for _, obj := range objs {
workload := obj.(*ext.ExternalWorkload)
workloads = append(workloads, workload)
}
return workloads, nil
}
func podNotTerminating(pod *corev1.Pod) bool {
phase := pod.Status.Phase
podTerminated := phase == corev1.PodSucceeded || phase == corev1.PodFailed
podTerminating := pod.DeletionTimestamp != nil
return !podTerminating && !podTerminated
}
package watcher
import (
"strconv"
"sync"
"time"
"github.com/linkerd/linkerd2/controller/k8s"
labels "github.com/linkerd/linkerd2/pkg/k8s"
"github.com/linkerd/linkerd2/pkg/util"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
logging "github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"
"k8s.io/client-go/tools/cache"
)
type (
// OpaquePortsWatcher watches all the services in the cluster. If the
// opaque ports annotation is added to a service, the watcher will update
// listeners—if any—subscribed to that service.
OpaquePortsWatcher struct {
subscriptions map[ServiceID]*svcSubscriptions
k8sAPI *k8s.API
subscribersGauge *prometheus.GaugeVec
log *logging.Entry
defaultOpaquePorts map[uint32]struct{}
sync.RWMutex
}
svcSubscriptions struct {
opaquePorts map[uint32]struct{}
listeners []OpaquePortsUpdateListener
}
// OpaquePortsUpdateListener is the interface that subscribers must implement.
OpaquePortsUpdateListener interface {
UpdateService(ports map[uint32]struct{})
}
)
var opaquePortsMetrics = promauto.NewGaugeVec(
prometheus.GaugeOpts{
Name: "service_subscribers",
Help: "Number of subscribers to Service changes.",
},
[]string{"namespace", "name"},
)
// NewOpaquePortsWatcher creates a OpaquePortsWatcher and begins watching for
// k8sAPI for service changes.
func NewOpaquePortsWatcher(k8sAPI *k8s.API, log *logging.Entry, opaquePorts map[uint32]struct{}) (*OpaquePortsWatcher, error) {
opw := &OpaquePortsWatcher{
subscriptions: make(map[ServiceID]*svcSubscriptions),
k8sAPI: k8sAPI,
subscribersGauge: opaquePortsMetrics,
log: log.WithField("component", "opaque-ports-watcher"),
defaultOpaquePorts: opaquePorts,
}
_, err := k8sAPI.Svc().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: opw.addService,
DeleteFunc: opw.deleteService,
UpdateFunc: opw.updateService,
})
if err != nil {
return nil, err
}
return opw, nil
}
// Subscribe subscribes a listener to a service; each time the service
// changes, the listener will be updated if the list of opaque ports
// changes.
func (opw *OpaquePortsWatcher) Subscribe(id ServiceID, listener OpaquePortsUpdateListener) error {
opw.Lock()
defer opw.Unlock()
svc, _ := opw.k8sAPI.Svc().Lister().Services(id.Namespace).Get(id.Name)
if svc != nil && svc.Spec.Type == corev1.ServiceTypeExternalName {
return invalidService(id.String())
}
opw.log.Debugf("Starting watch on service %s", id)
var numListeners float64
ss, ok := opw.subscriptions[id]
if !ok {
// If there is no watched service, create a subscription for the service
// and no opaque ports
opw.subscriptions[id] = &svcSubscriptions{
opaquePorts: opw.defaultOpaquePorts,
listeners: []OpaquePortsUpdateListener{listener},
}
numListeners = 1
} else {
// There are subscriptions for this service, so add the listener to the
// service listeners. If there are opaque ports for the service, update
// the listener with that value.
ss.listeners = append(ss.listeners, listener)
listener.UpdateService(ss.opaquePorts)
numListeners = float64(len(ss.listeners))
}
opw.subscribersGauge.With(id.Labels()).Set(numListeners)
return nil
}
// Unsubscribe unsubscribes a listener from service.
func (opw *OpaquePortsWatcher) Unsubscribe(id ServiceID, listener OpaquePortsUpdateListener) {
opw.Lock()
defer opw.Unlock()
opw.log.Debugf("Stopping watch on service %s", id)
ss, ok := opw.subscriptions[id]
if !ok {
opw.log.Errorf("Cannot unsubscribe from unknown service %s", id)
return
}
for i, l := range ss.listeners {
if l == listener {
n := len(ss.listeners)
ss.listeners[i] = ss.listeners[n-1]
ss.listeners[n-1] = nil
ss.listeners = ss.listeners[:n-1]
}
}
labels := id.Labels()
if len(ss.listeners) > 0 {
opw.subscribersGauge.With(labels).Set(float64(len(ss.listeners)))
} else {
if !opw.subscribersGauge.Delete(labels) {
opw.log.Warnf("unable to delete service_subscribers metric with labels %s", labels)
}
delete(opw.subscriptions, id)
}
}
func (opw *OpaquePortsWatcher) updateService(oldObj interface{}, newObj interface{}) {
newSvc := newObj.(*corev1.Service)
oldSvc := oldObj.(*corev1.Service)
oldUpdated := latestUpdated(oldSvc.ManagedFields)
updated := latestUpdated(newSvc.ManagedFields)
if !updated.IsZero() && updated != oldUpdated {
delta := time.Since(updated)
serviceInformerLag.Observe(delta.Seconds())
}
opw.addService(newObj)
}
func (opw *OpaquePortsWatcher) addService(obj interface{}) {
opw.Lock()
defer opw.Unlock()
svc := obj.(*corev1.Service)
id := ServiceID{
Namespace: svc.Namespace,
Name: svc.Name,
}
opaquePorts, ok, err := getServiceOpaquePortsAnnotation(svc)
if err != nil {
opw.log.Errorf("failed to get %s service opaque ports annotation: %s", id, err)
return
}
// If the opaque ports annotation was not set, then set the service's
// opaque ports to the default value.
if !ok {
opaquePorts = opw.defaultOpaquePorts
}
ss, ok := opw.subscriptions[id]
// If there are no subscriptions for this service, create one with the
// opaque ports.
if !ok {
opw.subscriptions[id] = &svcSubscriptions{
opaquePorts: opaquePorts,
listeners: []OpaquePortsUpdateListener{},
}
return
}
// Do not send updates if there was no change in the opaque ports; if
// there was, send an update to each listener.
if portsEqual(ss.opaquePorts, opaquePorts) {
return
}
ss.opaquePorts = opaquePorts
for _, listener := range ss.listeners {
listener.UpdateService(ss.opaquePorts)
}
}
func (opw *OpaquePortsWatcher) deleteService(obj interface{}) {
opw.Lock()
defer opw.Unlock()
service, ok := obj.(*corev1.Service)
if !ok {
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
if !ok {
opw.log.Errorf("could not get object from DeletedFinalStateUnknown %#v", obj)
return
}
service, ok = tombstone.Obj.(*corev1.Service)
if !ok {
opw.log.Errorf("DeletedFinalStateUnknown contained object that is not a Service %#v", obj)
return
}
}
id := ServiceID{
Namespace: service.Namespace,
Name: service.Name,
}
ss, ok := opw.subscriptions[id]
if !ok {
return
}
old := ss.opaquePorts
ss.opaquePorts = opw.defaultOpaquePorts
// Do not send an update if the service already had the default opaque ports
if portsEqual(old, ss.opaquePorts) {
return
}
for _, listener := range ss.listeners {
listener.UpdateService(ss.opaquePorts)
}
}
func getServiceOpaquePortsAnnotation(svc *corev1.Service) (map[uint32]struct{}, bool, error) {
annotation, ok := svc.Annotations[labels.ProxyOpaquePortsAnnotation]
if !ok {
return nil, false, nil
}
opaquePorts := make(map[uint32]struct{})
if annotation != "" {
for _, portStr := range parseServiceOpaquePorts(annotation, svc.Spec.Ports) {
port, err := strconv.ParseUint(portStr, 10, 32)
if err != nil {
return nil, true, err
}
opaquePorts[uint32(port)] = struct{}{}
}
}
return opaquePorts, true, nil
}
func parseServiceOpaquePorts(annotation string, sps []corev1.ServicePort) []string {
portRanges := util.GetPortRanges(annotation)
var values []string
for _, pr := range portRanges {
port, named := isNamed(pr, sps)
if named {
values = append(values, strconv.Itoa(int(port)))
} else {
pr, err := util.ParsePortRange(pr)
if err != nil {
logging.Warnf("Invalid port range [%v]: %s", pr, err)
continue
}
for i := pr.LowerBound; i <= pr.UpperBound; i++ {
values = append(values, strconv.Itoa(i))
}
}
}
return values
}
// isNamed checks if a port range is actually a service named port (e.g.
// `123-456` is a valid name, but also is a valid range); all port names must
// be checked before making it a list.
func isNamed(pr string, sps []corev1.ServicePort) (int32, bool) {
for _, sp := range sps {
if sp.Name == pr {
return sp.Port, true
}
}
return 0, false
}
func portsEqual(x, y map[uint32]struct{}) bool {
if len(x) != len(y) {
return false
}
for port := range x {
_, ok := y[port]
if !ok {
return false
}
}
return true
}
package watcher
import (
"sync"
"time"
sp "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
splisters "github.com/linkerd/linkerd2/controller/gen/client/listers/serviceprofile/v1alpha2"
"github.com/linkerd/linkerd2/controller/k8s"
"github.com/prometheus/client_golang/prometheus"
logging "github.com/sirupsen/logrus"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/client-go/tools/cache"
)
type (
// ProfileWatcher watches all service profiles in the Kubernetes cluster.
// Listeners can subscribe to a particular profile and profileWatcher will
// publish the service profile and all future changes for that profile.
ProfileWatcher struct {
profileLister splisters.ServiceProfileLister
profiles map[ProfileID]*profilePublisher // <-- intentional formatting error to test CI
log *logging.Entry
sync.RWMutex // This mutex protects modification of the map itself.
}
profilePublisher struct {
profile *sp.ServiceProfile
listeners []ProfileUpdateListener
log *logging.Entry
profileMetrics metrics
// All access to the profilePublisher is explicitly synchronized by this mutex.
sync.Mutex
}
// ProfileUpdateListener is the interface that subscribers must implement.
ProfileUpdateListener interface {
Update(profile *sp.ServiceProfile)
}
)
var profileVecs = newMetricsVecs("profile", []string{"namespace", "profile"})
// NewProfileWatcher creates a ProfileWatcher and begins watching the k8sAPI for
// service profile changes.
func NewProfileWatcher(k8sAPI *k8s.API, log *logging.Entry) (*ProfileWatcher, error) {
watcher := &ProfileWatcher{
profileLister: k8sAPI.SP().Lister(),
profiles: make(map[ProfileID]*profilePublisher),
log: log.WithField("component", "profile-watcher"),
}
_, err := k8sAPI.SP().Informer().AddEventHandler(
cache.ResourceEventHandlerFuncs{
AddFunc: watcher.addProfile,
UpdateFunc: watcher.updateProfile,
DeleteFunc: watcher.deleteProfile,
},
)
if err != nil {
return nil, err
}
return watcher, nil
}
//////////////////////
/// ProfileWatcher ///
//////////////////////
// Subscribe to an authority.
// The provided listener will be updated each time the service profile for the
// given authority is changed.
func (pw *ProfileWatcher) Subscribe(id ProfileID, listener ProfileUpdateListener) error {
pw.log.Debugf("Establishing watch on profile %s", id)
publisher := pw.getOrNewProfilePublisher(id, nil)
publisher.subscribe(listener)
return nil
}
// Unsubscribe removes a listener from the subscribers list for this authority.
func (pw *ProfileWatcher) Unsubscribe(id ProfileID, listener ProfileUpdateListener) {
pw.log.Debugf("Stopping watch on profile %s", id)
publisher, ok := pw.getProfilePublisher(id)
if !ok {
pw.log.Errorf("cannot unsubscribe from unknown service [%s]", id)
}
publisher.unsubscribe(listener)
}
func (pw *ProfileWatcher) addProfile(obj interface{}) {
profile := obj.(*sp.ServiceProfile)
id := ProfileID{
Namespace: profile.Namespace,
Name: profile.Name,
}
publisher := pw.getOrNewProfilePublisher(id, profile)
publisher.update(profile)
}
func (pw *ProfileWatcher) updateProfile(old interface{}, new interface{}) {
oldProfile := old.(*sp.ServiceProfile)
newProfile := new.(*sp.ServiceProfile)
oldUpdated := latestUpdated(oldProfile.ManagedFields)
updated := latestUpdated(newProfile.ManagedFields)
if !updated.IsZero() && updated != oldUpdated {
delta := time.Since(updated)
serviceProfileInformerLag.Observe(delta.Seconds())
}
pw.addProfile(new)
}
func (pw *ProfileWatcher) deleteProfile(obj interface{}) {
profile, ok := obj.(*sp.ServiceProfile)
if !ok {
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
if !ok {
pw.log.Errorf("couldn't get object from DeletedFinalStateUnknown %#v", obj)
return
}
profile, ok = tombstone.Obj.(*sp.ServiceProfile)
if !ok {
pw.log.Errorf("DeletedFinalStateUnknown contained object that is not a ServiceProfile %#v", obj)
return
}
}
id := ProfileID{
Namespace: profile.Namespace,
Name: profile.Name,
}
publisher, ok := pw.getProfilePublisher(id)
if ok {
publisher.update(nil)
}
}
func (pw *ProfileWatcher) getOrNewProfilePublisher(id ProfileID, profile *sp.ServiceProfile) *profilePublisher {
pw.Lock()
defer pw.Unlock()
publisher, ok := pw.profiles[id]
if !ok {
if profile == nil {
var err error
profile, err = pw.profileLister.ServiceProfiles(id.Namespace).Get(id.Name)
if err != nil && !apierrors.IsNotFound(err) {
pw.log.Errorf("error getting service profile: %s", err)
}
if err != nil {
profile = nil
}
}
publisher = &profilePublisher{
profile: profile,
listeners: make([]ProfileUpdateListener, 0),
log: pw.log.WithFields(logging.Fields{
"component": "profile-publisher",
"ns": id.Namespace,
"profile": id.Name,
}),
profileMetrics: profileVecs.newMetrics(prometheus.Labels{
"namespace": id.Namespace,
"profile": id.Name,
}),
}
pw.profiles[id] = publisher
}
return publisher
}
func (pw *ProfileWatcher) getProfilePublisher(id ProfileID) (publisher *profilePublisher, ok bool) {
pw.RLock()
defer pw.RUnlock()
publisher, ok = pw.profiles[id]
return
}
////////////////////////
/// profilePublisher ///
////////////////////////
func (pp *profilePublisher) subscribe(listener ProfileUpdateListener) {
pp.Lock()
defer pp.Unlock()
pp.listeners = append(pp.listeners, listener)
listener.Update(pp.profile)
pp.profileMetrics.setSubscribers(len(pp.listeners))
}
// unsubscribe returns true if and only if the listener was found and removed.
// it also returns the number of listeners remaining after unsubscribing.
func (pp *profilePublisher) unsubscribe(listener ProfileUpdateListener) {
pp.Lock()
defer pp.Unlock()
for i, item := range pp.listeners {
if item == listener {
// delete the item from the slice
n := len(pp.listeners)
pp.listeners[i] = pp.listeners[n-1]
pp.listeners[n-1] = nil
pp.listeners = pp.listeners[:n-1]
break
}
}
pp.profileMetrics.setSubscribers(len(pp.listeners))
}
func (pp *profilePublisher) update(profile *sp.ServiceProfile) {
pp.Lock()
defer pp.Unlock()
pp.log.Debug("Updating profile")
pp.profile = profile
for _, listener := range pp.listeners {
listener.Update(profile)
}
pp.profileMetrics.incUpdates()
}
package watcher
import (
"fmt"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
log "github.com/sirupsen/logrus"
)
type (
metricsVecs struct {
labelNames []string
subscribers *prometheus.GaugeVec
updates *prometheus.CounterVec
}
metrics struct {
labels prometheus.Labels
subscribers prometheus.Gauge
updates prometheus.Counter
}
endpointsMetricsVecs struct {
metricsVecs
pods *prometheus.GaugeVec
exists *prometheus.GaugeVec
}
endpointsMetrics struct {
metrics
pods prometheus.Gauge
exists prometheus.Gauge
}
)
var (
informer_lag_seconds_buckets = []float64{
0.5, // 500ms
1, // 1s
2.5, // 2.5s
5, // 5s
10, // 10s
25, // 25s
50, // 50s
100, // 1m 40s
250, // 4m 10s
1000, // 16m 40s
}
endpointsInformerLag = promauto.NewHistogram(
prometheus.HistogramOpts{
Name: "endpoints_informer_lag_seconds",
Help: "The amount of time between when an Endpoints resource is updated and when an informer observes it",
Buckets: informer_lag_seconds_buckets,
},
)
endpointsliceInformerLag = promauto.NewHistogram(
prometheus.HistogramOpts{
Name: "endpointslices_informer_lag_seconds",
Help: "The amount of time between when an EndpointSlice resource is updated and when an informer observes it",
Buckets: informer_lag_seconds_buckets,
},
)
serviceInformerLag = promauto.NewHistogram(
prometheus.HistogramOpts{
Name: "services_informer_lag_seconds",
Help: "The amount of time between when a Service resource is updated and when an informer observes it",
Buckets: informer_lag_seconds_buckets,
},
)
serverInformerLag = promauto.NewHistogram(
prometheus.HistogramOpts{
Name: "servers_informer_lag_seconds",
Help: "The amount of time between when a Server resource is updated and when an informer observes it",
Buckets: informer_lag_seconds_buckets,
},
)
podInformerLag = promauto.NewHistogram(
prometheus.HistogramOpts{
Name: "pods_informer_lag_seconds",
Help: "The amount of time between when a Pod resource is updated and when an informer observes it",
Buckets: informer_lag_seconds_buckets,
},
)
externalWorkloadInformerLag = promauto.NewHistogram(
prometheus.HistogramOpts{
Name: "externalworkload_informer_lag_seconds",
Help: "The amount of time between when an ExternalWorkload resource is updated and when an informer observes it",
Buckets: informer_lag_seconds_buckets,
},
)
serviceProfileInformerLag = promauto.NewHistogram(
prometheus.HistogramOpts{
Name: "serviceprofiles_informer_lag_seconds",
Help: "The amount of time between when a ServiceProfile resource is updated and when an informer observes it",
Buckets: informer_lag_seconds_buckets,
},
)
)
func newMetricsVecs(name string, labels []string) metricsVecs {
subscribers := promauto.NewGaugeVec(
prometheus.GaugeOpts{
Name: fmt.Sprintf("%s_subscribers", name),
Help: fmt.Sprintf("A gauge for the current number of subscribers to a %s.", name),
},
labels,
)
updates := promauto.NewCounterVec(
prometheus.CounterOpts{
Name: fmt.Sprintf("%s_updates", name),
Help: fmt.Sprintf("A counter for number of updates to a %s.", name),
},
labels,
)
return metricsVecs{
labelNames: labels,
subscribers: subscribers,
updates: updates,
}
}
func endpointsLabels(cluster, namespace, service, port string, hostname string) prometheus.Labels {
return prometheus.Labels{
"cluster": cluster,
"namespace": namespace,
"service": service,
"port": port,
"hostname": hostname,
}
}
func labelNames(labels prometheus.Labels) []string {
names := []string{}
for label := range labels {
names = append(names, label)
}
return names
}
func newEndpointsMetricsVecs() endpointsMetricsVecs {
labels := labelNames(endpointsLabels("", "", "", "", ""))
vecs := newMetricsVecs("endpoints", labels)
pods := promauto.NewGaugeVec(
prometheus.GaugeOpts{
Name: "endpoints_pods",
Help: "A gauge for the current number of pods in a endpoints.",
},
labels,
)
exists := promauto.NewGaugeVec(
prometheus.GaugeOpts{
Name: "endpoints_exists",
Help: "A gauge which is 1 if the endpoints exists and 0 if it does not.",
},
labels,
)
return endpointsMetricsVecs{
metricsVecs: vecs,
pods: pods,
exists: exists,
}
}
func (mv metricsVecs) newMetrics(labels prometheus.Labels) metrics {
return metrics{
labels: labels,
subscribers: mv.subscribers.With(labels),
updates: mv.updates.With(labels),
}
}
func (emv endpointsMetricsVecs) newEndpointsMetrics(labels prometheus.Labels) endpointsMetrics {
metrics := emv.newMetrics(labels)
return endpointsMetrics{
metrics: metrics,
pods: emv.pods.With(labels),
exists: emv.exists.With(labels),
}
}
func (emv endpointsMetricsVecs) unregister(labels prometheus.Labels) {
if !emv.metricsVecs.subscribers.Delete(labels) {
log.Warnf("unable to delete endpoints_subscribers metric with labels %s", labels)
}
if !emv.metricsVecs.updates.Delete(labels) {
log.Warnf("unable to delete endpoints_updates metric with labels %s", labels)
}
if !emv.pods.Delete(labels) {
log.Warnf("unable to delete endpoints_pods metric with labels %s", labels)
}
if !emv.exists.Delete(labels) {
log.Warnf("unable to delete endpoints_exists metric with labels %s", labels)
}
}
func (m metrics) setSubscribers(n int) {
m.subscribers.Set(float64(n))
}
func (m metrics) incUpdates() {
m.updates.Inc()
}
func (em endpointsMetrics) setPods(n int) {
em.pods.Set(float64(n))
}
func (em endpointsMetrics) setExists(exists bool) {
if exists {
em.exists.Set(1.0)
} else {
em.exists.Set(0.0)
}
}
package watcher
import (
"sync"
"testing"
"github.com/go-test/deep"
sp "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
"github.com/linkerd/linkerd2/controller/k8s"
)
// DeletingProfileListener implements ProfileUpdateListener and registers
// deletions. Useful for unit testing
type DeletingProfileListener struct {
NumDeletes int
}
// NewDeletingProfileListener creates a new NewDeletingProfileListener.
func NewDeletingProfileListener() *DeletingProfileListener {
return &DeletingProfileListener{
NumDeletes: 0,
}
}
// Update registers a deletion
func (dpl *DeletingProfileListener) Update(profile *sp.ServiceProfile) {
if profile == nil {
dpl.NumDeletes++
}
}
// BufferingProfileListener implements ProfileUpdateListener and stores updates
// in a slice. Useful for unit tests.
type BufferingProfileListener struct {
Profiles []*sp.ServiceProfile
mu sync.RWMutex
}
// NewBufferingProfileListener creates a new BufferingProfileListener.
func NewBufferingProfileListener() *BufferingProfileListener {
return &BufferingProfileListener{
Profiles: []*sp.ServiceProfile{},
}
}
func CreateMockDecoder(configs ...string) configDecoder {
// Create a mock decoder with some random objs to satisfy client creation
return func(data []byte, cluster string, enableEndpointSlices bool) (*k8s.API, *k8s.MetadataAPI, error) {
remoteAPI, err := k8s.NewFakeAPI(configs...)
if err != nil {
return nil, nil, err
}
metadataAPI, err := k8s.NewFakeMetadataAPI(nil)
if err != nil {
return nil, nil, err
}
return remoteAPI, metadataAPI, nil
}
}
// Update stores the update in the internal buffer.
func (bpl *BufferingProfileListener) Update(profile *sp.ServiceProfile) {
bpl.mu.Lock()
defer bpl.mu.Unlock()
bpl.Profiles = append(bpl.Profiles, profile)
}
func testCompare(t *testing.T, expected interface{}, actual interface{}) {
t.Helper()
if diff := deep.Equal(expected, actual); diff != nil {
t.Fatalf("%v", diff)
}
}
package watcher
import (
"context"
"fmt"
"net"
"strconv"
"strings"
"sync"
"time"
ext "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1"
"github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta2"
"github.com/linkerd/linkerd2/controller/k8s"
consts "github.com/linkerd/linkerd2/pkg/k8s"
"github.com/linkerd/linkerd2/pkg/util"
"github.com/prometheus/client_golang/prometheus"
logging "github.com/sirupsen/logrus"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
corev1 "k8s.io/api/core/v1"
discovery "k8s.io/api/discovery/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
)
type (
// WorkloadWatcher watches all pods and externalworkloads in the cluster.
// It keeps a map of publishers keyed by IP and port.
WorkloadWatcher struct {
defaultOpaquePorts map[uint32]struct{}
k8sAPI *k8s.API
metadataAPI *k8s.MetadataAPI
publishers map[IPPort]*workloadPublisher
log *logging.Entry
enableEndpointSlices bool
mu sync.RWMutex
}
// workloadPublisher represents an address including ip:port, the backing
// pod or externalworkload (if any), and if the protocol is opaque. It keeps
// a list of listeners to be notified whenever the workload or the
// associated opaque protocol config changes.
workloadPublisher struct {
defaultOpaquePorts map[uint32]struct{}
k8sAPI *k8s.API
metadataAPI *k8s.MetadataAPI
addr Address
listeners []WorkloadUpdateListener
metrics metrics
log *logging.Entry
mu sync.RWMutex
}
// PodUpdateListener is the interface subscribers must implement.
WorkloadUpdateListener interface {
Update(*Address) error
}
)
var ipPortVecs = newMetricsVecs("ip_port", []string{"ip", "port"})
func NewWorkloadWatcher(k8sAPI *k8s.API, metadataAPI *k8s.MetadataAPI, log *logging.Entry, enableEndpointSlices bool, defaultOpaquePorts map[uint32]struct{}) (*WorkloadWatcher, error) {
ww := &WorkloadWatcher{
defaultOpaquePorts: defaultOpaquePorts,
k8sAPI: k8sAPI,
metadataAPI: metadataAPI,
publishers: make(map[IPPort]*workloadPublisher),
log: log.WithFields(logging.Fields{
"component": "workload-watcher",
}),
enableEndpointSlices: enableEndpointSlices,
}
_, err := k8sAPI.Pod().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: ww.addPod,
DeleteFunc: ww.deletePod,
UpdateFunc: ww.updatePod,
})
if err != nil {
return nil, err
}
_, err = k8sAPI.ExtWorkload().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: ww.addExternalWorkload,
DeleteFunc: ww.deleteExternalWorkload,
UpdateFunc: ww.updateExternalWorkload,
})
if err != nil {
return nil, err
}
_, err = k8sAPI.Srv().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: ww.addOrDeleteServer,
DeleteFunc: ww.addOrDeleteServer,
UpdateFunc: ww.updateServer,
})
if err != nil {
return nil, err
}
return ww, nil
}
// Subscribe notifies the listener on changes on any workload backing the passed
// host/ip:port or changes to its associated opaque protocol config. If service
// and hostname are empty then ip should be set and vice-versa. If ip is empty,
// the corresponding ip is found for the given service/hostname, and returned.
func (ww *WorkloadWatcher) Subscribe(service *ServiceID, hostname, ip string, port Port, listener WorkloadUpdateListener) (string, error) {
if hostname != "" {
ww.log.Debugf("Establishing watch on workload %s.%s.%s:%d", hostname, service.Name, service.Namespace, port)
} else if service != nil {
ww.log.Debugf("Establishing watch on workload %s.%s:%d", service.Name, service.Namespace, port)
} else {
ww.log.Debugf("Establishing watch on workload %s:%d", ip, port)
}
wp, err := ww.getOrNewWorkloadPublisher(service, hostname, ip, port)
if err != nil {
return "", err
}
if err = wp.subscribe(listener); err != nil {
return "", err
}
return wp.addr.IP, nil
}
// Subscribe stops notifying the listener on chages on any pod backing the
// passed ip:port or its associated protocol config
func (ww *WorkloadWatcher) Unsubscribe(ip string, port Port, listener WorkloadUpdateListener) {
ww.mu.Lock()
defer ww.mu.Unlock()
ww.log.Debugf("Stopping watch on %s:%d", ip, port)
wp, ok := ww.getWorkloadPublisher(ip, port)
if !ok {
ww.log.Errorf("Cannot unsubscribe from unknown ip:port [%s:%d]", ip, port)
return
}
wp.unsubscribe(listener)
if len(wp.listeners) == 0 {
delete(ww.publishers, IPPort{wp.addr.IP, wp.addr.Port})
}
}
// addPod is an event handler so it cannot block
func (ww *WorkloadWatcher) addPod(obj any) {
pod := obj.(*corev1.Pod)
ww.log.Tracef("Added pod %s.%s", pod.Name, pod.Namespace)
go ww.submitPodUpdate(pod, false)
}
// deletePod is an event handler so it cannot block
func (ww *WorkloadWatcher) deletePod(obj any) {
pod, ok := obj.(*corev1.Pod)
if !ok {
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
if !ok {
ww.log.Errorf("Couldn't get object from DeletedFinalStateUnknown %#v", obj)
return
}
pod, ok = tombstone.Obj.(*corev1.Pod)
if !ok {
ww.log.Errorf("DeletedFinalStateUnknown contained object that is not a Pod %#v", obj)
return
}
}
ww.log.Tracef("Deleted pod %s.%s", pod.Name, pod.Namespace)
go ww.submitPodUpdate(pod, true)
}
// updatePod is an event handler so it cannot block
func (ww *WorkloadWatcher) updatePod(oldObj any, newObj any) {
oldPod := oldObj.(*corev1.Pod)
newPod := newObj.(*corev1.Pod)
if oldPod.DeletionTimestamp == nil && newPod.DeletionTimestamp != nil {
// this is just a mark, wait for actual deletion event
return
}
oldUpdated := latestUpdated(oldPod.ManagedFields)
updated := latestUpdated(newPod.ManagedFields)
if !updated.IsZero() && updated != oldUpdated {
delta := time.Since(updated)
podInformerLag.Observe(delta.Seconds())
}
ww.log.Tracef("Updated pod %s.%s", newPod.Name, newPod.Namespace)
go ww.submitPodUpdate(newPod, false)
}
// addExternalWorkload is an event handler so it cannot block
func (ww *WorkloadWatcher) addExternalWorkload(obj any) {
externalWorkload := obj.(*ext.ExternalWorkload)
ww.log.Tracef("Added externalworkload %s.%s", externalWorkload.Name, externalWorkload.Namespace)
go ww.submitExternalWorkloadUpdate(externalWorkload, false)
}
// deleteExternalWorkload is an event handler so it cannot block
func (ww *WorkloadWatcher) deleteExternalWorkload(obj any) {
externalWorkload, ok := obj.(*ext.ExternalWorkload)
if !ok {
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
if !ok {
ww.log.Errorf("Couldn't get object from DeletedFinalStateUnknown %#v", obj)
return
}
externalWorkload, ok = tombstone.Obj.(*ext.ExternalWorkload)
if !ok {
ww.log.Errorf("DeletedFinalStateUnknown contained object that is not an ExternalWorkload %#v", obj)
return
}
}
ww.log.Tracef("Deleted externalworklod %s.%s", externalWorkload.Name, externalWorkload.Namespace)
go ww.submitExternalWorkloadUpdate(externalWorkload, true)
}
// updateExternalWorkload is an event handler so it cannot block
func (ww *WorkloadWatcher) updateExternalWorkload(oldObj any, newObj any) {
oldExternalWorkload := oldObj.(*ext.ExternalWorkload)
newExternalWorkload := newObj.(*ext.ExternalWorkload)
if oldExternalWorkload.DeletionTimestamp == nil && newExternalWorkload.DeletionTimestamp != nil {
// this is just a mark, wait for actual deletion event
return
}
oldUpdated := latestUpdated(oldExternalWorkload.ManagedFields)
updated := latestUpdated(newExternalWorkload.ManagedFields)
if !updated.IsZero() && updated != oldUpdated {
delta := time.Since(updated)
externalWorkloadInformerLag.Observe(delta.Seconds())
}
ww.log.Tracef("Updated pod %s.%s", newExternalWorkload.Name, newExternalWorkload.Namespace)
go ww.submitExternalWorkloadUpdate(newExternalWorkload, false)
}
func (ww *WorkloadWatcher) submitPodUpdate(pod *corev1.Pod, remove bool) {
ww.mu.RLock()
defer ww.mu.RUnlock()
submitPod := pod
if remove {
submitPod = nil
}
for _, container := range pod.Spec.Containers {
for _, containerPort := range container.Ports {
if containerPort.ContainerPort != 0 {
for _, pip := range pod.Status.PodIPs {
if wp, ok := ww.getWorkloadPublisher(pip.IP, Port(containerPort.ContainerPort)); ok {
wp.updatePod(submitPod)
}
}
if len(pod.Status.PodIPs) == 0 && pod.Status.PodIP != "" {
if wp, ok := ww.getWorkloadPublisher(pod.Status.PodIP, Port(containerPort.ContainerPort)); ok {
wp.updatePod(submitPod)
}
}
}
if containerPort.HostPort != 0 {
for _, hip := range pod.Status.HostIPs {
if pp, ok := ww.getWorkloadPublisher(hip.IP, Port(containerPort.HostPort)); ok {
pp.updatePod(submitPod)
}
}
if len(pod.Status.HostIPs) == 0 && pod.Status.HostIP != "" {
if pp, ok := ww.getWorkloadPublisher(pod.Status.HostIP, Port(containerPort.HostPort)); ok {
pp.updatePod(submitPod)
}
}
}
}
}
}
func (ww *WorkloadWatcher) submitExternalWorkloadUpdate(externalWorkload *ext.ExternalWorkload, remove bool) {
ww.mu.RLock()
defer ww.mu.RUnlock()
submitWorkload := externalWorkload
if remove {
submitWorkload = nil
}
for _, port := range externalWorkload.Spec.Ports {
for _, ip := range externalWorkload.Spec.WorkloadIPs {
if wp, ok := ww.getWorkloadPublisher(ip.Ip, Port(port.Port)); ok {
wp.updateExternalWorkload(submitWorkload)
}
}
}
}
func (ww *WorkloadWatcher) updateServer(oldObj interface{}, newObj interface{}) {
oldServer := oldObj.(*v1beta2.Server)
newServer := newObj.(*v1beta2.Server)
oldUpdated := latestUpdated(oldServer.ManagedFields)
updated := latestUpdated(newServer.ManagedFields)
if !updated.IsZero() && updated != oldUpdated {
delta := time.Since(updated)
serverInformerLag.Observe(delta.Seconds())
}
ww.updateServers(oldServer, newServer)
}
func (ww *WorkloadWatcher) addOrDeleteServer(obj interface{}) {
server, ok := obj.(*v1beta2.Server)
if !ok {
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
if !ok {
ww.log.Errorf("Couldn't get object from DeletedFinalStateUnknown %#v", obj)
return
}
server, ok = tombstone.Obj.(*v1beta2.Server)
if !ok {
ww.log.Errorf("DeletedFinalStateUnknown contained object that is not a Server %#v", obj)
return
}
}
ww.updateServers(server)
}
// updateServers triggers an Update() call to the listeners of the workloadPublishers
// whose pod matches the any of the Servers' podSelector or whose
// externalworkload matches any of the Servers' externalworkload selection. This
// function is an event handler so it cannot block.
func (ww *WorkloadWatcher) updateServers(servers ...*v1beta2.Server) {
ww.mu.RLock()
defer ww.mu.RUnlock()
for _, wp := range ww.publishers {
var opaquePorts map[uint32]struct{}
if wp.addr.Pod != nil {
if !ww.isPodSelectedByAny(wp.addr.Pod, servers...) {
continue
}
opaquePorts = GetAnnotatedOpaquePorts(wp.addr.Pod, ww.defaultOpaquePorts)
} else if wp.addr.ExternalWorkload != nil {
if !ww.isExternalWorkloadSelectedByAny(wp.addr.ExternalWorkload, servers...) {
continue
}
opaquePorts = GetAnnotatedOpaquePortsForExternalWorkload(wp.addr.ExternalWorkload, ww.defaultOpaquePorts)
} else {
continue
}
_, annotatedOpaque := opaquePorts[wp.addr.Port]
// if port is annotated to be always opaque we can disregard Server updates
if annotatedOpaque {
continue
}
opaque := wp.addr.OpaqueProtocol
name := net.JoinHostPort(wp.addr.IP, fmt.Sprintf("%d", wp.addr.Port))
if wp.addr.Pod != nil {
name = wp.addr.Pod.GetName()
} else if wp.addr.ExternalWorkload != nil {
name = wp.addr.ExternalWorkload.GetName()
}
if err := SetToServerProtocol(wp.k8sAPI, &wp.addr); err != nil {
wp.log.Errorf("Error computing opaque protocol for %s: %q", name, err)
}
if wp.addr.OpaqueProtocol == opaque {
// OpaqueProtocol has not changed. No need to update the listeners.
continue
}
go func(wp *workloadPublisher) {
wp.mu.RLock()
defer wp.mu.RUnlock()
for _, listener := range wp.listeners {
if err := listener.Update(&wp.addr); err != nil {
ww.log.Warnf("Error sending update to listener: %s", err)
continue
}
}
wp.metrics.incUpdates()
}(wp)
}
}
func (ww *WorkloadWatcher) isPodSelectedByAny(pod *corev1.Pod, servers ...*v1beta2.Server) bool {
for _, s := range servers {
selector, err := metav1.LabelSelectorAsSelector(s.Spec.PodSelector)
if err != nil {
ww.log.Errorf("failed to parse PodSelector of Server %s.%s: %q", s.GetName(), s.GetNamespace(), err)
continue
}
if selector.Matches(labels.Set(pod.Labels)) {
return true
}
}
return false
}
func (ww *WorkloadWatcher) isExternalWorkloadSelectedByAny(ew *ext.ExternalWorkload, servers ...*v1beta2.Server) bool {
for _, s := range servers {
selector, err := metav1.LabelSelectorAsSelector(s.Spec.ExternalWorkloadSelector)
if err != nil {
ww.log.Errorf("failed to parse ExternalWorkloadSelector of Server %s.%s: %q", s.GetName(), s.GetNamespace(), err)
continue
}
if selector.Matches(labels.Set(ew.Labels)) {
return true
}
}
return false
}
// getOrNewWorkloadPublisher returns the workloadPublisher for the given target if it
// exists. Otherwise, it creates a new one and returns it.
func (ww *WorkloadWatcher) getOrNewWorkloadPublisher(service *ServiceID, hostname, ip string, port Port) (*workloadPublisher, error) {
ww.mu.Lock()
defer ww.mu.Unlock()
var pod *corev1.Pod
var externalWorkload *ext.ExternalWorkload
var err error
if hostname != "" {
pod, err = ww.getEndpointByHostname(hostname, service)
if err != nil {
return nil, err
}
ip = pod.Status.PodIP
} else {
pod, err = ww.getPodByPodIP(ip, port)
if err != nil {
return nil, err
}
if pod == nil {
pod, err = ww.getPodByHostIP(ip, port)
if err != nil {
return nil, err
}
}
if pod == nil {
externalWorkload, err = ww.getExternalWorkloadByIP(ip, port)
if err != nil {
return nil, err
}
}
}
ipPort := IPPort{ip, port}
wp, ok := ww.publishers[ipPort]
if !ok {
wp = &workloadPublisher{
defaultOpaquePorts: ww.defaultOpaquePorts,
k8sAPI: ww.k8sAPI,
metadataAPI: ww.metadataAPI,
addr: Address{
IP: ip,
Port: port,
},
metrics: ipPortVecs.newMetrics(prometheus.Labels{
"ip": ip,
"port": strconv.FormatUint(uint64(port), 10),
}),
log: ww.log.WithFields(logging.Fields{
"component": "workload-publisher",
"ip": ip,
"port": port,
}),
}
if pod != nil {
wp.updatePod(pod)
}
if externalWorkload != nil {
wp.updateExternalWorkload(externalWorkload)
}
ww.publishers[ipPort] = wp
}
return wp, nil
}
func (ww *WorkloadWatcher) getWorkloadPublisher(ip string, port Port) (wp *workloadPublisher, ok bool) {
ipPort := IPPort{ip, port}
wp, ok = ww.publishers[ipPort]
return
}
// getPodByPodIP returns a pod that maps to the given IP address in the pod network
func (ww *WorkloadWatcher) getPodByPodIP(podIP string, port uint32) (*corev1.Pod, error) {
podIPPods, err := getIndexedPods(ww.k8sAPI, PodIPIndex, podIP)
if err != nil {
return nil, status.Error(codes.Unknown, err.Error())
}
if len(podIPPods) == 1 {
ww.log.Debugf("found %s on the pod network", podIP)
return podIPPods[0], nil
}
if len(podIPPods) > 1 {
conflictingPods := []string{}
for _, pod := range podIPPods {
conflictingPods = append(conflictingPods, fmt.Sprintf("%s:%s", pod.Namespace, pod.Name))
}
ww.log.Warnf("found conflicting %s IP on the pod network: %s", podIP, strings.Join(conflictingPods, ","))
return nil, status.Errorf(codes.FailedPrecondition, "found %d pods with a conflicting pod network IP %s", len(podIPPods), podIP)
}
ww.log.Debugf("no pod found for %s:%d", podIP, port)
return nil, nil
}
// getPodByHostIP returns a pod that maps to the given IP address in the host
// network. It must have a container port that exposes `port` as a host port.
func (ww *WorkloadWatcher) getPodByHostIP(hostIP string, port uint32) (*corev1.Pod, error) {
addr := net.JoinHostPort(hostIP, fmt.Sprintf("%d", port))
hostIPPods, err := getIndexedPods(ww.k8sAPI, HostIPIndex, addr)
if err != nil {
return nil, status.Error(codes.Unknown, err.Error())
}
if len(hostIPPods) == 1 {
ww.log.Debugf("found %s:%d on the host network", hostIP, port)
return hostIPPods[0], nil
}
if len(hostIPPods) > 1 {
conflictingPods := []string{}
for _, pod := range hostIPPods {
conflictingPods = append(conflictingPods, fmt.Sprintf("%s:%s", pod.Namespace, pod.Name))
}
ww.log.Warnf("found conflicting %s:%d endpoint on the host network: %s", hostIP, port, strings.Join(conflictingPods, ","))
return nil, status.Errorf(codes.FailedPrecondition, "found %d pods with a conflicting host network endpoint %s:%d", len(hostIPPods), hostIP, port)
}
return nil, nil
}
// getExternalWorkloadByIP returns an externalworkload with the given IP
// address.
func (ww *WorkloadWatcher) getExternalWorkloadByIP(ip string, port uint32) (*ext.ExternalWorkload, error) {
addr := net.JoinHostPort(ip, fmt.Sprintf("%d", port))
workloads, err := getIndexedExternalWorkloads(ww.k8sAPI, ExternalWorkloadIPIndex, addr)
if err != nil {
return nil, status.Error(codes.Unknown, err.Error())
}
if len(workloads) == 0 {
ww.log.Debugf("no externalworkload found for %s:%d", ip, port)
return nil, nil
}
if len(workloads) == 1 {
ww.log.Debugf("found externalworkload %s:%d", ip, port)
return workloads[0], nil
}
if len(workloads) > 1 {
conflictingWorkloads := []string{}
for _, ew := range workloads {
conflictingWorkloads = append(conflictingWorkloads, fmt.Sprintf("%s:%s", ew.Namespace, ew.Name))
}
ww.log.Warnf("found conflicting %s:%d externalworkload: %s", ip, port, strings.Join(conflictingWorkloads, ","))
return nil, status.Errorf(codes.FailedPrecondition, "found %d externalworkloads with a conflicting ip %s:%d", len(workloads), ip, port)
}
return nil, nil
}
// getEndpointByHostname returns a pod that maps to the given hostname (or an
// instanceID). The hostname is generally the prefix of the pod's DNS name;
// since it may be arbitrary we need to look at the corresponding service's
// Endpoints object to see whether the hostname matches a pod.
func (ww *WorkloadWatcher) getEndpointByHostname(hostname string, svcID *ServiceID) (*corev1.Pod, error) {
if ww.enableEndpointSlices {
matchLabels := map[string]string{discovery.LabelServiceName: svcID.Name}
selector := labels.Set(matchLabels).AsSelector()
sliceList, err := ww.k8sAPI.ES().Lister().EndpointSlices(svcID.Namespace).List(selector)
if err != nil {
return nil, err
}
for _, slice := range sliceList {
for _, ep := range slice.Endpoints {
if ep.Hostname != nil && hostname == *ep.Hostname {
if ep.TargetRef != nil && ep.TargetRef.Kind == "Pod" {
podName := ep.TargetRef.Name
podNamespace := ep.TargetRef.Namespace
pod, err := ww.k8sAPI.Pod().Lister().Pods(podNamespace).Get(podName)
if err != nil {
return nil, err
}
return pod, nil
}
return nil, nil
}
}
}
return nil, status.Errorf(codes.NotFound, "no pod found in EndpointSlices of Service %s/%s for hostname %s", svcID.Namespace, svcID.Name, hostname)
}
ep, err := ww.k8sAPI.Endpoint().Lister().Endpoints(svcID.Namespace).Get(svcID.Name)
if err != nil {
return nil, err
}
for _, subset := range ep.Subsets {
for _, addr := range subset.Addresses {
if hostname == addr.Hostname {
if addr.TargetRef != nil && addr.TargetRef.Kind == "Pod" {
podName := addr.TargetRef.Name
podNamespace := addr.TargetRef.Namespace
pod, err := ww.k8sAPI.Pod().Lister().Pods(podNamespace).Get(podName)
if err != nil {
return nil, err
}
return pod, nil
}
return nil, nil
}
}
}
return nil, status.Errorf(codes.NotFound, "no pod found in Endpoints %s/%s for hostname %s", svcID.Namespace, svcID.Name, hostname)
}
func (wp *workloadPublisher) subscribe(listener WorkloadUpdateListener) error {
wp.mu.Lock()
defer wp.mu.Unlock()
wp.listeners = append(wp.listeners, listener)
wp.metrics.setSubscribers(len(wp.listeners))
if err := listener.Update(&wp.addr); err != nil {
return fmt.Errorf("failed to send initial update: %w", err)
}
wp.metrics.incUpdates()
return nil
}
func (wp *workloadPublisher) unsubscribe(listener WorkloadUpdateListener) {
wp.mu.Lock()
defer wp.mu.Unlock()
for i, e := range wp.listeners {
if e == listener {
n := len(wp.listeners)
wp.listeners[i] = wp.listeners[n-1]
wp.listeners[n-1] = nil
wp.listeners = wp.listeners[:n-1]
break
}
}
wp.metrics.setSubscribers(len(wp.listeners))
}
// updatePod creates an Address instance for the given pod, that is passed to
// the listener's Update() method, only if the pod's readiness state has
// changed. If the passed pod is nil, it means the pod (still referred to in
// wp.pod) has been deleted.
func (wp *workloadPublisher) updatePod(pod *corev1.Pod) {
wp.mu.Lock()
defer wp.mu.Unlock()
// pod wasn't ready or there was no backing pod - check if passed pod is ready
if wp.addr.Pod == nil {
if pod == nil {
wp.log.Trace("Pod deletion event already consumed - ignore")
return
}
if !isRunningAndReady(pod) {
wp.log.Tracef("Pod %s.%s not ready - ignore", pod.Name, pod.Namespace)
return
}
wp.log.Debugf("Pod %s.%s became ready", pod.Name, pod.Namespace)
wp.addr.Pod = pod
// Fill in ownership.
if wp.addr.Pod != nil {
ownerKind, ownerName, err := wp.metadataAPI.GetOwnerKindAndName(context.Background(), wp.addr.Pod, true)
if err != nil {
wp.log.Errorf("Error getting pod owner for pod %s: %q", wp.addr.Pod.GetName(), err)
} else {
wp.addr.OwnerKind = ownerKind
wp.addr.OwnerName = ownerName
}
}
// Compute opaque protocol.
if err := SetToServerProtocol(wp.k8sAPI, &wp.addr); err != nil {
wp.log.Errorf("Error computing opaque protocol for pod %s: %q", wp.addr.Pod.GetName(), err)
}
for _, l := range wp.listeners {
if err := l.Update(&wp.addr); err != nil {
wp.log.Warnf("Error sending update to listener: %s", err)
continue
}
}
wp.metrics.incUpdates()
return
}
// backing pod becoming unready or getting deleted
if pod == nil || !isRunningAndReady(pod) {
wp.log.Debugf("Pod %s.%s deleted or it became unready - remove", wp.addr.Pod.Name, wp.addr.Pod.Namespace)
wp.addr.Pod = nil
wp.addr.OwnerKind = ""
wp.addr.OwnerName = ""
wp.addr.OpaqueProtocol = false
for _, l := range wp.listeners {
if err := l.Update(&wp.addr); err != nil {
wp.log.Warnf("Error sending update to listener: %s", err)
continue
}
}
wp.metrics.incUpdates()
return
}
wp.log.Tracef("Ignored event on pod %s.%s", pod.Name, pod.Namespace)
}
// updateExternalWorkload creates an Address instance for the given externalworkload,
// that is passed to the listener's Update() method, only if the workload's
// readiness state has changed. If the passed workload is nil, it means the
// workload (still referred to in wp.externalWorkload) has been deleted.
func (wp *workloadPublisher) updateExternalWorkload(externalWorkload *ext.ExternalWorkload) {
wp.mu.Lock()
defer wp.mu.Unlock()
wp.addr.ExternalWorkload = externalWorkload
// Fill in ownership.
if wp.addr.ExternalWorkload != nil && len(wp.addr.ExternalWorkload.GetOwnerReferences()) == 1 {
wp.addr.OwnerKind = wp.addr.ExternalWorkload.GetOwnerReferences()[0].Kind
wp.addr.OwnerName = wp.addr.ExternalWorkload.GetOwnerReferences()[0].Name
}
// Compute opaque protocol.
if err := SetToServerProtocolExternalWorkload(wp.k8sAPI, &wp.addr); err != nil {
wp.log.Errorf("Error computing opaque protocol for externalworkload %s: %q", wp.addr.ExternalWorkload.GetName(), err)
}
for _, l := range wp.listeners {
if err := l.Update(&wp.addr); err != nil {
wp.log.Warnf("Error sending update to listener: %s", err)
continue
}
}
wp.metrics.incUpdates()
}
// GetAnnotatedOpaquePorts returns the opaque ports for the pod given its
// annotations, or the default opaque ports if it's not annotated
func GetAnnotatedOpaquePorts(pod *corev1.Pod, defaultPorts map[uint32]struct{}) map[uint32]struct{} {
if pod == nil {
return defaultPorts
}
annotation, ok := pod.Annotations[consts.ProxyOpaquePortsAnnotation]
if !ok {
return defaultPorts
}
opaquePorts := make(map[uint32]struct{})
namedPorts := util.GetNamedPorts(pod.Spec.Containers)
if annotation != "" {
for _, pr := range util.ParseContainerOpaquePorts(annotation, namedPorts) {
for _, port := range pr.Ports() {
opaquePorts[uint32(port)] = struct{}{}
}
}
}
return opaquePorts
}
// GetAnnotatedOpaquePortsForExternalWorkload returns the opaque ports for the external workload given its
// annotations, or the default opaque ports if it's not annotated
func GetAnnotatedOpaquePortsForExternalWorkload(ew *ext.ExternalWorkload, defaultPorts map[uint32]struct{}) map[uint32]struct{} {
if ew == nil {
return defaultPorts
}
annotation, ok := ew.Annotations[consts.ProxyOpaquePortsAnnotation]
if !ok {
return defaultPorts
}
opaquePorts := make(map[uint32]struct{})
if annotation != "" {
for _, pr := range parseExternalWorkloadOpaquePorts(annotation, ew) {
for _, port := range pr.Ports() {
opaquePorts[uint32(port)] = struct{}{}
}
}
}
return opaquePorts
}
func parseExternalWorkloadOpaquePorts(override string, ew *ext.ExternalWorkload) []util.PortRange {
portRanges := util.GetPortRanges(override)
var values []util.PortRange
for _, pr := range portRanges {
port, named := isNamedInExternalWorkload(pr, ew)
if named {
values = append(values, util.PortRange{UpperBound: int(port), LowerBound: int(port)})
} else {
pr, err := util.ParsePortRange(pr)
if err != nil {
logging.Warnf("Invalid port range [%v]: %s", pr, err)
continue
}
values = append(values, pr)
}
}
return values
}
func isNamedInExternalWorkload(pr string, ew *ext.ExternalWorkload) (int32, bool) {
for _, p := range ew.Spec.Ports {
if p.Name == pr {
return p.Port, true
}
}
return 0, false
}
func isRunningAndReady(pod *corev1.Pod) bool {
if pod == nil || pod.Status.Phase != corev1.PodRunning {
return false
}
for _, condition := range pod.Status.Conditions {
if condition.Type == corev1.PodReady && condition.Status == corev1.ConditionTrue {
return true
}
}
return false
}
package util
import (
"errors"
"io"
"sync"
destinationPb "github.com/linkerd/linkerd2-proxy-api/go/destination"
"github.com/linkerd/linkerd2-proxy-api/go/net"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
type mockStream struct {
ctx context.Context
Cancel context.CancelFunc
}
func newMockStream() mockStream {
ctx, cancel := context.WithCancel(context.Background())
return mockStream{ctx, cancel}
}
func (ms mockStream) Context() context.Context { return ms.ctx }
func (ms mockStream) SendMsg(m interface{}) error { return nil }
func (ms mockStream) RecvMsg(m interface{}) error { return nil }
// MockServerStream satisfies the grpc.ServerStream interface
type MockServerStream struct{ mockStream }
// SetHeader satisfies the grpc.ServerStream interface
func (mss MockServerStream) SetHeader(metadata.MD) error { return nil }
// SendHeader satisfies the grpc.ServerStream interface
func (mss MockServerStream) SendHeader(metadata.MD) error { return nil }
// SetTrailer satisfies the grpc.ServerStream interface
func (mss MockServerStream) SetTrailer(metadata.MD) {}
// NewMockServerStream instantiates a MockServerStream
func NewMockServerStream() MockServerStream {
return MockServerStream{newMockStream()}
}
// MockAPIClient satisfies the destination API's interfaces
type MockAPIClient struct {
ErrorToReturn error
DestinationGetClientToReturn destinationPb.Destination_GetClient
}
// Get provides a mock of a destination API method.
func (c *MockAPIClient) Get(ctx context.Context, in *destinationPb.GetDestination, opts ...grpc.CallOption) (destinationPb.Destination_GetClient, error) {
return c.DestinationGetClientToReturn, c.ErrorToReturn
}
// GetProfile provides a mock of a destination API method
func (c *MockAPIClient) GetProfile(ctx context.Context, _ *destinationPb.GetDestination, _ ...grpc.CallOption) (destinationPb.Destination_GetProfileClient, error) {
// Not implemented through this client. The proxies use the gRPC server directly instead.
return nil, errors.New("not implemented")
}
// MockDestinationGetClient satisfies the Destination_GetClient gRPC interface.
type MockDestinationGetClient struct {
UpdatesToReturn []destinationPb.Update
ErrorsToReturn []error
grpc.ClientStream
sync.Mutex
}
// Recv satisfies the Destination_GetClient.Recv() gRPC method.
func (a *MockDestinationGetClient) Recv() (*destinationPb.Update, error) {
a.Lock()
defer a.Unlock()
var updatePopped *destinationPb.Update
var errorPopped error
if len(a.UpdatesToReturn) == 0 && len(a.ErrorsToReturn) == 0 {
return nil, io.EOF
}
if len(a.UpdatesToReturn) != 0 {
updatePopped, a.UpdatesToReturn = &a.UpdatesToReturn[0], a.UpdatesToReturn[1:]
}
if len(a.ErrorsToReturn) != 0 {
errorPopped, a.ErrorsToReturn = a.ErrorsToReturn[0], a.ErrorsToReturn[1:]
}
return updatePopped, errorPopped
}
// AuthorityEndpoints holds the details for the Endpoints associated to an authority
type AuthorityEndpoints struct {
Namespace string
ServiceID string
Pods []PodDetails
}
// PodDetails holds the details for pod associated to an Endpoint
type PodDetails struct {
Name string
IP uint32
Port uint32
}
// BuildAddrSet converts AuthorityEndpoints into its protobuf representation
func BuildAddrSet(endpoint AuthorityEndpoints) *destinationPb.WeightedAddrSet {
addrs := make([]*destinationPb.WeightedAddr, 0)
for _, pod := range endpoint.Pods {
addr := &net.TcpAddress{
Ip: &net.IPAddress{Ip: &net.IPAddress_Ipv4{Ipv4: pod.IP}},
Port: pod.Port,
}
labels := map[string]string{"pod": pod.Name}
weightedAddr := &destinationPb.WeightedAddr{Addr: addr, MetricLabels: labels}
addrs = append(addrs, weightedAddr)
}
labels := map[string]string{"namespace": endpoint.Namespace, "service": endpoint.ServiceID}
return &destinationPb.WeightedAddrSet{Addrs: addrs, MetricLabels: labels}
}
package v1beta1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/linkerd/linkerd2/controller/gen/apis/externalworkload"
)
var (
// SchemeGroupVersion is the identifier for the API which includes the name
// of the group and the version of the API.
SchemeGroupVersion = schema.GroupVersion{
Group: externalworkload.GroupName,
Version: "v1beta1",
}
// SchemeBuilder collects functions that add things to a scheme. It's to
// allow code to compile without explicitly referencing generated types.
// You should declare one in each package that will have generated deep
// copy or conversion functions.
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
// AddToScheme applies all the stored functions to the scheme. A non-nil error
// indicates that one function failed and the attempt was abandoned.
AddToScheme = SchemeBuilder.AddToScheme
)
// Kind takes an unqualified kind and returns back a Group qualified GroupKind
func Kind(kind string) schema.GroupKind {
return SchemeGroupVersion.WithKind(kind).GroupKind()
}
// Resource takes an unqualified resource and returns a Group qualified
// GroupResource
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}
// Adds the list of known types to Scheme.
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&ExternalWorkload{},
&ExternalWorkloadList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
}
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1beta1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ExternalWorkload) DeepCopyInto(out *ExternalWorkload) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalWorkload.
func (in *ExternalWorkload) DeepCopy() *ExternalWorkload {
if in == nil {
return nil
}
out := new(ExternalWorkload)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ExternalWorkload) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ExternalWorkloadList) DeepCopyInto(out *ExternalWorkloadList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]ExternalWorkload, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalWorkloadList.
func (in *ExternalWorkloadList) DeepCopy() *ExternalWorkloadList {
if in == nil {
return nil
}
out := new(ExternalWorkloadList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ExternalWorkloadList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ExternalWorkloadSpec) DeepCopyInto(out *ExternalWorkloadSpec) {
*out = *in
out.MeshTLS = in.MeshTLS
if in.Ports != nil {
in, out := &in.Ports, &out.Ports
*out = make([]PortSpec, len(*in))
copy(*out, *in)
}
if in.WorkloadIPs != nil {
in, out := &in.WorkloadIPs, &out.WorkloadIPs
*out = make([]WorkloadIP, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalWorkloadSpec.
func (in *ExternalWorkloadSpec) DeepCopy() *ExternalWorkloadSpec {
if in == nil {
return nil
}
out := new(ExternalWorkloadSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ExternalWorkloadStatus) DeepCopyInto(out *ExternalWorkloadStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]WorkloadCondition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalWorkloadStatus.
func (in *ExternalWorkloadStatus) DeepCopy() *ExternalWorkloadStatus {
if in == nil {
return nil
}
out := new(ExternalWorkloadStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MeshTLS) DeepCopyInto(out *MeshTLS) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshTLS.
func (in *MeshTLS) DeepCopy() *MeshTLS {
if in == nil {
return nil
}
out := new(MeshTLS)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PortSpec) DeepCopyInto(out *PortSpec) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PortSpec.
func (in *PortSpec) DeepCopy() *PortSpec {
if in == nil {
return nil
}
out := new(PortSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *WorkloadCondition) DeepCopyInto(out *WorkloadCondition) {
*out = *in
in.LastProbeTime.DeepCopyInto(&out.LastProbeTime)
in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkloadCondition.
func (in *WorkloadCondition) DeepCopy() *WorkloadCondition {
if in == nil {
return nil
}
out := new(WorkloadCondition)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *WorkloadIP) DeepCopyInto(out *WorkloadIP) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkloadIP.
func (in *WorkloadIP) DeepCopy() *WorkloadIP {
if in == nil {
return nil
}
out := new(WorkloadIP)
in.DeepCopyInto(out)
return out
}
package v1alpha1
import (
"github.com/linkerd/linkerd2/controller/gen/apis/link"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
var (
// SchemeGroupVersion is the identifier for the API which includes the name
// of the group and the version of the API.
SchemeGroupVersion = schema.GroupVersion{
Group: link.GroupName,
Version: "v1alpha1",
}
// SchemeBuilder collects functions that add things to a scheme. It's to
// allow code to compile without explicitly referencing generated types.
// You should declare one in each package that will have generated deep
// copy or conversion functions.
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
// AddToScheme applies all the stored functions to the scheme. A non-nil error
// indicates that one function failed and the attempt was abandoned.
AddToScheme = SchemeBuilder.AddToScheme
)
// Kind takes an unqualified kind and returns back a Group qualified GroupKind
func Kind(kind string) schema.GroupKind {
return SchemeGroupVersion.WithKind(kind).GroupKind()
}
// Resource takes an unqualified resource and returns a Group qualified
// GroupResource
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}
// Adds the list of known types to Scheme.
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&Link{},
&LinkList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
}
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1alpha1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Link) DeepCopyInto(out *Link) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Link.
func (in *Link) DeepCopy() *Link {
if in == nil {
return nil
}
out := new(Link)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Link) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LinkList) DeepCopyInto(out *LinkList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Link, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinkList.
func (in *LinkList) DeepCopy() *LinkList {
if in == nil {
return nil
}
out := new(LinkList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *LinkList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LinkSpec) DeepCopyInto(out *LinkSpec) {
*out = *in
out.ProbeSpec = in.ProbeSpec
in.Selector.DeepCopyInto(&out.Selector)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinkSpec.
func (in *LinkSpec) DeepCopy() *LinkSpec {
if in == nil {
return nil
}
out := new(LinkSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ProbeSpec) DeepCopyInto(out *ProbeSpec) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProbeSpec.
func (in *ProbeSpec) DeepCopy() *ProbeSpec {
if in == nil {
return nil
}
out := new(ProbeSpec)
in.DeepCopyInto(out)
return out
}
package v1alpha1
import (
"github.com/linkerd/linkerd2/controller/gen/apis/policy"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
var (
// SchemeGroupVersion is the identifier for the API which includes the name
// of the group and the version of the API.
SchemeGroupVersion = schema.GroupVersion{
Group: policy.GroupName,
Version: "v1alpha1",
}
// SchemeBuilder collects functions that add things to a scheme. It's to
// allow code to compile without explicitly referencing generated types.
// You should declare one in each package that will have generated deep
// copy or conversion functions.
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
// AddToScheme applies all the stored functions to the scheme. A non-nil error
// indicates that one function failed and the attempt was abandoned.
AddToScheme = SchemeBuilder.AddToScheme
)
// Kind takes an unqualified kind and returns back a Group qualified GroupKind
func Kind(kind string) schema.GroupKind {
return SchemeGroupVersion.WithKind(kind).GroupKind()
}
// Resource takes an unqualified resource and returns a Group qualified
// GroupResource
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}
// Adds the list of known types to Scheme.
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&AuthorizationPolicy{},
&AuthorizationPolicyList{},
&HTTPRoute{},
&HTTPRouteList{},
&MeshTLSAuthentication{},
&MeshTLSAuthenticationList{},
&NetworkAuthentication{},
&NetworkAuthenticationList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
}
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1alpha1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
v1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
v1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AuthorizationPolicy) DeepCopyInto(out *AuthorizationPolicy) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthorizationPolicy.
func (in *AuthorizationPolicy) DeepCopy() *AuthorizationPolicy {
if in == nil {
return nil
}
out := new(AuthorizationPolicy)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *AuthorizationPolicy) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AuthorizationPolicyList) DeepCopyInto(out *AuthorizationPolicyList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]AuthorizationPolicy, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthorizationPolicyList.
func (in *AuthorizationPolicyList) DeepCopy() *AuthorizationPolicyList {
if in == nil {
return nil
}
out := new(AuthorizationPolicyList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *AuthorizationPolicyList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AuthorizationPolicySpec) DeepCopyInto(out *AuthorizationPolicySpec) {
*out = *in
in.TargetRef.DeepCopyInto(&out.TargetRef)
if in.RequiredAuthenticationRefs != nil {
in, out := &in.RequiredAuthenticationRefs, &out.RequiredAuthenticationRefs
*out = make([]v1alpha2.PolicyTargetReference, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthorizationPolicySpec.
func (in *AuthorizationPolicySpec) DeepCopy() *AuthorizationPolicySpec {
if in == nil {
return nil
}
out := new(AuthorizationPolicySpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPHeader) DeepCopyInto(out *HTTPHeader) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPHeader.
func (in *HTTPHeader) DeepCopy() *HTTPHeader {
if in == nil {
return nil
}
out := new(HTTPHeader)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPHeaderMatch) DeepCopyInto(out *HTTPHeaderMatch) {
*out = *in
if in.Type != nil {
in, out := &in.Type, &out.Type
*out = new(HeaderMatchType)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPHeaderMatch.
func (in *HTTPHeaderMatch) DeepCopy() *HTTPHeaderMatch {
if in == nil {
return nil
}
out := new(HTTPHeaderMatch)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPPathMatch) DeepCopyInto(out *HTTPPathMatch) {
*out = *in
if in.Type != nil {
in, out := &in.Type, &out.Type
*out = new(PathMatchType)
**out = **in
}
if in.Value != nil {
in, out := &in.Value, &out.Value
*out = new(string)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPPathMatch.
func (in *HTTPPathMatch) DeepCopy() *HTTPPathMatch {
if in == nil {
return nil
}
out := new(HTTPPathMatch)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPPathModifier) DeepCopyInto(out *HTTPPathModifier) {
*out = *in
if in.ReplaceFullPath != nil {
in, out := &in.ReplaceFullPath, &out.ReplaceFullPath
*out = new(string)
**out = **in
}
if in.ReplacePrefixMatch != nil {
in, out := &in.ReplacePrefixMatch, &out.ReplacePrefixMatch
*out = new(string)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPPathModifier.
func (in *HTTPPathModifier) DeepCopy() *HTTPPathModifier {
if in == nil {
return nil
}
out := new(HTTPPathModifier)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPQueryParamMatch) DeepCopyInto(out *HTTPQueryParamMatch) {
*out = *in
if in.Type != nil {
in, out := &in.Type, &out.Type
*out = new(QueryParamMatchType)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPQueryParamMatch.
func (in *HTTPQueryParamMatch) DeepCopy() *HTTPQueryParamMatch {
if in == nil {
return nil
}
out := new(HTTPQueryParamMatch)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPRequestHeaderFilter) DeepCopyInto(out *HTTPRequestHeaderFilter) {
*out = *in
if in.Set != nil {
in, out := &in.Set, &out.Set
*out = make([]HTTPHeader, len(*in))
copy(*out, *in)
}
if in.Add != nil {
in, out := &in.Add, &out.Add
*out = make([]HTTPHeader, len(*in))
copy(*out, *in)
}
if in.Remove != nil {
in, out := &in.Remove, &out.Remove
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRequestHeaderFilter.
func (in *HTTPRequestHeaderFilter) DeepCopy() *HTTPRequestHeaderFilter {
if in == nil {
return nil
}
out := new(HTTPRequestHeaderFilter)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPRequestRedirectFilter) DeepCopyInto(out *HTTPRequestRedirectFilter) {
*out = *in
if in.Scheme != nil {
in, out := &in.Scheme, &out.Scheme
*out = new(string)
**out = **in
}
if in.Hostname != nil {
in, out := &in.Hostname, &out.Hostname
*out = new(v1beta1.PreciseHostname)
**out = **in
}
if in.Path != nil {
in, out := &in.Path, &out.Path
*out = new(HTTPPathModifier)
(*in).DeepCopyInto(*out)
}
if in.Port != nil {
in, out := &in.Port, &out.Port
*out = new(v1beta1.PortNumber)
**out = **in
}
if in.StatusCode != nil {
in, out := &in.StatusCode, &out.StatusCode
*out = new(int)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRequestRedirectFilter.
func (in *HTTPRequestRedirectFilter) DeepCopy() *HTTPRequestRedirectFilter {
if in == nil {
return nil
}
out := new(HTTPRequestRedirectFilter)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPRoute) DeepCopyInto(out *HTTPRoute) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRoute.
func (in *HTTPRoute) DeepCopy() *HTTPRoute {
if in == nil {
return nil
}
out := new(HTTPRoute)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *HTTPRoute) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPRouteFilter) DeepCopyInto(out *HTTPRouteFilter) {
*out = *in
if in.RequestHeaderModifier != nil {
in, out := &in.RequestHeaderModifier, &out.RequestHeaderModifier
*out = new(HTTPRequestHeaderFilter)
(*in).DeepCopyInto(*out)
}
if in.RequestRedirect != nil {
in, out := &in.RequestRedirect, &out.RequestRedirect
*out = new(HTTPRequestRedirectFilter)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRouteFilter.
func (in *HTTPRouteFilter) DeepCopy() *HTTPRouteFilter {
if in == nil {
return nil
}
out := new(HTTPRouteFilter)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPRouteList) DeepCopyInto(out *HTTPRouteList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]HTTPRoute, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRouteList.
func (in *HTTPRouteList) DeepCopy() *HTTPRouteList {
if in == nil {
return nil
}
out := new(HTTPRouteList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *HTTPRouteList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPRouteMatch) DeepCopyInto(out *HTTPRouteMatch) {
*out = *in
if in.Path != nil {
in, out := &in.Path, &out.Path
*out = new(HTTPPathMatch)
(*in).DeepCopyInto(*out)
}
if in.Headers != nil {
in, out := &in.Headers, &out.Headers
*out = make([]HTTPHeaderMatch, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.QueryParams != nil {
in, out := &in.QueryParams, &out.QueryParams
*out = make([]HTTPQueryParamMatch, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Method != nil {
in, out := &in.Method, &out.Method
*out = new(HTTPMethod)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRouteMatch.
func (in *HTTPRouteMatch) DeepCopy() *HTTPRouteMatch {
if in == nil {
return nil
}
out := new(HTTPRouteMatch)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPRouteRule) DeepCopyInto(out *HTTPRouteRule) {
*out = *in
if in.Matches != nil {
in, out := &in.Matches, &out.Matches
*out = make([]HTTPRouteMatch, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Filters != nil {
in, out := &in.Filters, &out.Filters
*out = make([]HTTPRouteFilter, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRouteRule.
func (in *HTTPRouteRule) DeepCopy() *HTTPRouteRule {
if in == nil {
return nil
}
out := new(HTTPRouteRule)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPRouteSpec) DeepCopyInto(out *HTTPRouteSpec) {
*out = *in
in.CommonRouteSpec.DeepCopyInto(&out.CommonRouteSpec)
if in.Hostnames != nil {
in, out := &in.Hostnames, &out.Hostnames
*out = make([]v1beta1.Hostname, len(*in))
copy(*out, *in)
}
if in.Rules != nil {
in, out := &in.Rules, &out.Rules
*out = make([]HTTPRouteRule, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRouteSpec.
func (in *HTTPRouteSpec) DeepCopy() *HTTPRouteSpec {
if in == nil {
return nil
}
out := new(HTTPRouteSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPRouteStatus) DeepCopyInto(out *HTTPRouteStatus) {
*out = *in
in.RouteStatus.DeepCopyInto(&out.RouteStatus)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRouteStatus.
func (in *HTTPRouteStatus) DeepCopy() *HTTPRouteStatus {
if in == nil {
return nil
}
out := new(HTTPRouteStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MeshTLSAuthentication) DeepCopyInto(out *MeshTLSAuthentication) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshTLSAuthentication.
func (in *MeshTLSAuthentication) DeepCopy() *MeshTLSAuthentication {
if in == nil {
return nil
}
out := new(MeshTLSAuthentication)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *MeshTLSAuthentication) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MeshTLSAuthenticationList) DeepCopyInto(out *MeshTLSAuthenticationList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]MeshTLSAuthentication, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshTLSAuthenticationList.
func (in *MeshTLSAuthenticationList) DeepCopy() *MeshTLSAuthenticationList {
if in == nil {
return nil
}
out := new(MeshTLSAuthenticationList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *MeshTLSAuthenticationList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MeshTLSAuthenticationSpec) DeepCopyInto(out *MeshTLSAuthenticationSpec) {
*out = *in
if in.Identities != nil {
in, out := &in.Identities, &out.Identities
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.IdentityRefs != nil {
in, out := &in.IdentityRefs, &out.IdentityRefs
*out = make([]v1alpha2.PolicyTargetReference, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshTLSAuthenticationSpec.
func (in *MeshTLSAuthenticationSpec) DeepCopy() *MeshTLSAuthenticationSpec {
if in == nil {
return nil
}
out := new(MeshTLSAuthenticationSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Network) DeepCopyInto(out *Network) {
*out = *in
if in.Except != nil {
in, out := &in.Except, &out.Except
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Network.
func (in *Network) DeepCopy() *Network {
if in == nil {
return nil
}
out := new(Network)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NetworkAuthentication) DeepCopyInto(out *NetworkAuthentication) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkAuthentication.
func (in *NetworkAuthentication) DeepCopy() *NetworkAuthentication {
if in == nil {
return nil
}
out := new(NetworkAuthentication)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *NetworkAuthentication) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NetworkAuthenticationList) DeepCopyInto(out *NetworkAuthenticationList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]NetworkAuthentication, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkAuthenticationList.
func (in *NetworkAuthenticationList) DeepCopy() *NetworkAuthenticationList {
if in == nil {
return nil
}
out := new(NetworkAuthenticationList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *NetworkAuthenticationList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NetworkAuthenticationSpec) DeepCopyInto(out *NetworkAuthenticationSpec) {
*out = *in
if in.Networks != nil {
in, out := &in.Networks, &out.Networks
*out = make([]*Network, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Network)
(*in).DeepCopyInto(*out)
}
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkAuthenticationSpec.
func (in *NetworkAuthenticationSpec) DeepCopy() *NetworkAuthenticationSpec {
if in == nil {
return nil
}
out := new(NetworkAuthenticationSpec)
in.DeepCopyInto(out)
return out
}
package v1beta3
import (
"github.com/linkerd/linkerd2/controller/gen/apis/policy"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
var (
// SchemeGroupVersion is the identifier for the API which includes the name
// of the group and the version of the API.
SchemeGroupVersion = schema.GroupVersion{
Group: policy.GroupName,
Version: "v1beta3",
}
// SchemeBuilder collects functions that add things to a scheme. It's to
// allow code to compile without explicitly referencing generated types.
// You should declare one in each package that will have generated deep
// copy or conversion functions.
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
// AddToScheme applies all the stored functions to the scheme. A non-nil error
// indicates that one function failed and the attempt was abandoned.
AddToScheme = SchemeBuilder.AddToScheme
)
// Kind takes an unqualified kind and returns back a Group qualified GroupKind
func Kind(kind string) schema.GroupKind {
return SchemeGroupVersion.WithKind(kind).GroupKind()
}
// Resource takes an unqualified resource and returns a Group qualified
// GroupResource
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}
// Adds the list of known types to Scheme.
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&HTTPRoute{},
&HTTPRouteList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
}
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1beta3
import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
v1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPHeader) DeepCopyInto(out *HTTPHeader) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPHeader.
func (in *HTTPHeader) DeepCopy() *HTTPHeader {
if in == nil {
return nil
}
out := new(HTTPHeader)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPHeaderMatch) DeepCopyInto(out *HTTPHeaderMatch) {
*out = *in
if in.Type != nil {
in, out := &in.Type, &out.Type
*out = new(HeaderMatchType)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPHeaderMatch.
func (in *HTTPHeaderMatch) DeepCopy() *HTTPHeaderMatch {
if in == nil {
return nil
}
out := new(HTTPHeaderMatch)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPPathMatch) DeepCopyInto(out *HTTPPathMatch) {
*out = *in
if in.Type != nil {
in, out := &in.Type, &out.Type
*out = new(PathMatchType)
**out = **in
}
if in.Value != nil {
in, out := &in.Value, &out.Value
*out = new(string)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPPathMatch.
func (in *HTTPPathMatch) DeepCopy() *HTTPPathMatch {
if in == nil {
return nil
}
out := new(HTTPPathMatch)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPPathModifier) DeepCopyInto(out *HTTPPathModifier) {
*out = *in
if in.ReplaceFullPath != nil {
in, out := &in.ReplaceFullPath, &out.ReplaceFullPath
*out = new(string)
**out = **in
}
if in.ReplacePrefixMatch != nil {
in, out := &in.ReplacePrefixMatch, &out.ReplacePrefixMatch
*out = new(string)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPPathModifier.
func (in *HTTPPathModifier) DeepCopy() *HTTPPathModifier {
if in == nil {
return nil
}
out := new(HTTPPathModifier)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPQueryParamMatch) DeepCopyInto(out *HTTPQueryParamMatch) {
*out = *in
if in.Type != nil {
in, out := &in.Type, &out.Type
*out = new(QueryParamMatchType)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPQueryParamMatch.
func (in *HTTPQueryParamMatch) DeepCopy() *HTTPQueryParamMatch {
if in == nil {
return nil
}
out := new(HTTPQueryParamMatch)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPRequestHeaderFilter) DeepCopyInto(out *HTTPRequestHeaderFilter) {
*out = *in
if in.Set != nil {
in, out := &in.Set, &out.Set
*out = make([]HTTPHeader, len(*in))
copy(*out, *in)
}
if in.Add != nil {
in, out := &in.Add, &out.Add
*out = make([]HTTPHeader, len(*in))
copy(*out, *in)
}
if in.Remove != nil {
in, out := &in.Remove, &out.Remove
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRequestHeaderFilter.
func (in *HTTPRequestHeaderFilter) DeepCopy() *HTTPRequestHeaderFilter {
if in == nil {
return nil
}
out := new(HTTPRequestHeaderFilter)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPRequestRedirectFilter) DeepCopyInto(out *HTTPRequestRedirectFilter) {
*out = *in
if in.Scheme != nil {
in, out := &in.Scheme, &out.Scheme
*out = new(string)
**out = **in
}
if in.Hostname != nil {
in, out := &in.Hostname, &out.Hostname
*out = new(v1beta1.PreciseHostname)
**out = **in
}
if in.Path != nil {
in, out := &in.Path, &out.Path
*out = new(HTTPPathModifier)
(*in).DeepCopyInto(*out)
}
if in.Port != nil {
in, out := &in.Port, &out.Port
*out = new(v1beta1.PortNumber)
**out = **in
}
if in.StatusCode != nil {
in, out := &in.StatusCode, &out.StatusCode
*out = new(int)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRequestRedirectFilter.
func (in *HTTPRequestRedirectFilter) DeepCopy() *HTTPRequestRedirectFilter {
if in == nil {
return nil
}
out := new(HTTPRequestRedirectFilter)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPRoute) DeepCopyInto(out *HTTPRoute) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRoute.
func (in *HTTPRoute) DeepCopy() *HTTPRoute {
if in == nil {
return nil
}
out := new(HTTPRoute)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *HTTPRoute) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPRouteFilter) DeepCopyInto(out *HTTPRouteFilter) {
*out = *in
if in.RequestHeaderModifier != nil {
in, out := &in.RequestHeaderModifier, &out.RequestHeaderModifier
*out = new(HTTPRequestHeaderFilter)
(*in).DeepCopyInto(*out)
}
if in.RequestRedirect != nil {
in, out := &in.RequestRedirect, &out.RequestRedirect
*out = new(HTTPRequestRedirectFilter)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRouteFilter.
func (in *HTTPRouteFilter) DeepCopy() *HTTPRouteFilter {
if in == nil {
return nil
}
out := new(HTTPRouteFilter)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPRouteList) DeepCopyInto(out *HTTPRouteList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]HTTPRoute, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRouteList.
func (in *HTTPRouteList) DeepCopy() *HTTPRouteList {
if in == nil {
return nil
}
out := new(HTTPRouteList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *HTTPRouteList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPRouteMatch) DeepCopyInto(out *HTTPRouteMatch) {
*out = *in
if in.Path != nil {
in, out := &in.Path, &out.Path
*out = new(HTTPPathMatch)
(*in).DeepCopyInto(*out)
}
if in.Headers != nil {
in, out := &in.Headers, &out.Headers
*out = make([]HTTPHeaderMatch, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.QueryParams != nil {
in, out := &in.QueryParams, &out.QueryParams
*out = make([]HTTPQueryParamMatch, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Method != nil {
in, out := &in.Method, &out.Method
*out = new(HTTPMethod)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRouteMatch.
func (in *HTTPRouteMatch) DeepCopy() *HTTPRouteMatch {
if in == nil {
return nil
}
out := new(HTTPRouteMatch)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPRouteRule) DeepCopyInto(out *HTTPRouteRule) {
*out = *in
if in.Matches != nil {
in, out := &in.Matches, &out.Matches
*out = make([]HTTPRouteMatch, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Filters != nil {
in, out := &in.Filters, &out.Filters
*out = make([]HTTPRouteFilter, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.BackendRefs != nil {
in, out := &in.BackendRefs, &out.BackendRefs
*out = make([]v1beta1.HTTPBackendRef, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Timeouts != nil {
in, out := &in.Timeouts, &out.Timeouts
*out = new(HTTPRouteTimeouts)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRouteRule.
func (in *HTTPRouteRule) DeepCopy() *HTTPRouteRule {
if in == nil {
return nil
}
out := new(HTTPRouteRule)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPRouteSpec) DeepCopyInto(out *HTTPRouteSpec) {
*out = *in
in.CommonRouteSpec.DeepCopyInto(&out.CommonRouteSpec)
if in.Hostnames != nil {
in, out := &in.Hostnames, &out.Hostnames
*out = make([]v1beta1.Hostname, len(*in))
copy(*out, *in)
}
if in.Rules != nil {
in, out := &in.Rules, &out.Rules
*out = make([]HTTPRouteRule, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRouteSpec.
func (in *HTTPRouteSpec) DeepCopy() *HTTPRouteSpec {
if in == nil {
return nil
}
out := new(HTTPRouteSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPRouteStatus) DeepCopyInto(out *HTTPRouteStatus) {
*out = *in
in.RouteStatus.DeepCopyInto(&out.RouteStatus)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRouteStatus.
func (in *HTTPRouteStatus) DeepCopy() *HTTPRouteStatus {
if in == nil {
return nil
}
out := new(HTTPRouteStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPRouteTimeouts) DeepCopyInto(out *HTTPRouteTimeouts) {
*out = *in
if in.Request != nil {
in, out := &in.Request, &out.Request
*out = new(v1.Duration)
**out = **in
}
if in.BackendRequest != nil {
in, out := &in.BackendRequest, &out.BackendRequest
*out = new(v1.Duration)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRouteTimeouts.
func (in *HTTPRouteTimeouts) DeepCopy() *HTTPRouteTimeouts {
if in == nil {
return nil
}
out := new(HTTPRouteTimeouts)
in.DeepCopyInto(out)
return out
}
package v1beta1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/linkerd/linkerd2/controller/gen/apis/server"
)
var (
// SchemeGroupVersion is the identifier for the API which includes the name
// of the group and the version of the API.
SchemeGroupVersion = schema.GroupVersion{
Group: server.GroupName,
Version: "v1beta1",
}
// SchemeBuilder collects functions that add things to a scheme. It's to
// allow code to compile without explicitly referencing generated types.
// You should declare one in each package that will have generated deep
// copy or conversion functions.
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
// AddToScheme applies all the stored functions to the scheme. A non-nil error
// indicates that one function failed and the attempt was abandoned.
AddToScheme = SchemeBuilder.AddToScheme
)
// Kind takes an unqualified kind and returns back a Group qualified GroupKind
func Kind(kind string) schema.GroupKind {
return SchemeGroupVersion.WithKind(kind).GroupKind()
}
// Resource takes an unqualified resource and returns a Group qualified
// GroupResource
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}
// Adds the list of known types to Scheme.
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&Server{},
&ServerList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
}
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1beta1
import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Server) DeepCopyInto(out *Server) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Server.
func (in *Server) DeepCopy() *Server {
if in == nil {
return nil
}
out := new(Server)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Server) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServerList) DeepCopyInto(out *ServerList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Server, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerList.
func (in *ServerList) DeepCopy() *ServerList {
if in == nil {
return nil
}
out := new(ServerList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ServerList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServerSpec) DeepCopyInto(out *ServerSpec) {
*out = *in
if in.PodSelector != nil {
in, out := &in.PodSelector, &out.PodSelector
*out = new(v1.LabelSelector)
(*in).DeepCopyInto(*out)
}
out.Port = in.Port
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerSpec.
func (in *ServerSpec) DeepCopy() *ServerSpec {
if in == nil {
return nil
}
out := new(ServerSpec)
in.DeepCopyInto(out)
return out
}
package v1beta2
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/linkerd/linkerd2/controller/gen/apis/server"
)
var (
// SchemeGroupVersion is the identifier for the API which includes the name
// of the group and the version of the API.
SchemeGroupVersion = schema.GroupVersion{
Group: server.GroupName,
Version: "v1beta2",
}
// SchemeBuilder collects functions that add things to a scheme. It's to
// allow code to compile without explicitly referencing generated types.
// You should declare one in each package that will have generated deep
// copy or conversion functions.
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
// AddToScheme applies all the stored functions to the scheme. A non-nil error
// indicates that one function failed and the attempt was abandoned.
AddToScheme = SchemeBuilder.AddToScheme
)
// Kind takes an unqualified kind and returns back a Group qualified GroupKind
func Kind(kind string) schema.GroupKind {
return SchemeGroupVersion.WithKind(kind).GroupKind()
}
// Resource takes an unqualified resource and returns a Group qualified
// GroupResource
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}
// Adds the list of known types to Scheme.
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&Server{},
&ServerList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
}
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1beta2
import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Server) DeepCopyInto(out *Server) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Server.
func (in *Server) DeepCopy() *Server {
if in == nil {
return nil
}
out := new(Server)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Server) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServerList) DeepCopyInto(out *ServerList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Server, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerList.
func (in *ServerList) DeepCopy() *ServerList {
if in == nil {
return nil
}
out := new(ServerList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ServerList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServerSpec) DeepCopyInto(out *ServerSpec) {
*out = *in
if in.PodSelector != nil {
in, out := &in.PodSelector, &out.PodSelector
*out = new(v1.LabelSelector)
(*in).DeepCopyInto(*out)
}
if in.ExternalWorkloadSelector != nil {
in, out := &in.ExternalWorkloadSelector, &out.ExternalWorkloadSelector
*out = new(v1.LabelSelector)
(*in).DeepCopyInto(*out)
}
out.Port = in.Port
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerSpec.
func (in *ServerSpec) DeepCopy() *ServerSpec {
if in == nil {
return nil
}
out := new(ServerSpec)
in.DeepCopyInto(out)
return out
}
package v1beta1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/linkerd/linkerd2/controller/gen/apis/serverauthorization"
)
var (
// SchemeGroupVersion is the identifier for the API which includes the name
// of the group and the version of the API.
SchemeGroupVersion = schema.GroupVersion{
Group: serverauthorization.GroupName,
Version: "v1beta1",
}
// SchemeBuilder collects functions that add things to a scheme. It's to
// allow code to compile without explicitly referencing generated types.
// You should declare one in each package that will have generated deep
// copy or conversion functions.
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
// AddToScheme applies all the stored functions to the scheme. A non-nil error
// indicates that one function failed and the attempt was abandoned.
AddToScheme = SchemeBuilder.AddToScheme
)
// Kind takes an unqualified kind and returns back a Group qualified GroupKind
func Kind(kind string) schema.GroupKind {
return SchemeGroupVersion.WithKind(kind).GroupKind()
}
// Resource takes an unqualified resource and returns a Group qualified
// GroupResource
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}
// Adds the list of known types to Scheme.
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&ServerAuthorization{},
&ServerAuthorizationList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
}
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1beta1
import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Cidr) DeepCopyInto(out *Cidr) {
*out = *in
if in.Except != nil {
in, out := &in.Except, &out.Except
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Cidr.
func (in *Cidr) DeepCopy() *Cidr {
if in == nil {
return nil
}
out := new(Cidr)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Client) DeepCopyInto(out *Client) {
*out = *in
if in.Networks != nil {
in, out := &in.Networks, &out.Networks
*out = make([]*Cidr, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Cidr)
(*in).DeepCopyInto(*out)
}
}
}
if in.MeshTLS != nil {
in, out := &in.MeshTLS, &out.MeshTLS
*out = new(MeshTLS)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Client.
func (in *Client) DeepCopy() *Client {
if in == nil {
return nil
}
out := new(Client)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MeshTLS) DeepCopyInto(out *MeshTLS) {
*out = *in
if in.Identities != nil {
in, out := &in.Identities, &out.Identities
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.ServiceAccounts != nil {
in, out := &in.ServiceAccounts, &out.ServiceAccounts
*out = make([]*ServiceAccountName, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(ServiceAccountName)
**out = **in
}
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshTLS.
func (in *MeshTLS) DeepCopy() *MeshTLS {
if in == nil {
return nil
}
out := new(MeshTLS)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Server) DeepCopyInto(out *Server) {
*out = *in
if in.Selector != nil {
in, out := &in.Selector, &out.Selector
*out = new(v1.LabelSelector)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Server.
func (in *Server) DeepCopy() *Server {
if in == nil {
return nil
}
out := new(Server)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServerAuthorization) DeepCopyInto(out *ServerAuthorization) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerAuthorization.
func (in *ServerAuthorization) DeepCopy() *ServerAuthorization {
if in == nil {
return nil
}
out := new(ServerAuthorization)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ServerAuthorization) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServerAuthorizationList) DeepCopyInto(out *ServerAuthorizationList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]ServerAuthorization, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerAuthorizationList.
func (in *ServerAuthorizationList) DeepCopy() *ServerAuthorizationList {
if in == nil {
return nil
}
out := new(ServerAuthorizationList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ServerAuthorizationList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServerAuthorizationSpec) DeepCopyInto(out *ServerAuthorizationSpec) {
*out = *in
in.Server.DeepCopyInto(&out.Server)
in.Client.DeepCopyInto(&out.Client)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerAuthorizationSpec.
func (in *ServerAuthorizationSpec) DeepCopy() *ServerAuthorizationSpec {
if in == nil {
return nil
}
out := new(ServerAuthorizationSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceAccountName) DeepCopyInto(out *ServiceAccountName) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccountName.
func (in *ServiceAccountName) DeepCopy() *ServiceAccountName {
if in == nil {
return nil
}
out := new(ServiceAccountName)
in.DeepCopyInto(out)
return out
}
package v1alpha2
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile"
)
// SchemeGroupVersion is the identifier for the API which includes
// the name of the group and the version of the API
var SchemeGroupVersion = schema.GroupVersion{
Group: serviceprofile.GroupName,
Version: "v1alpha2",
}
// Kind takes an unqualified kind and returns back a Group qualified GroupKind
func Kind(kind string) schema.GroupKind {
return SchemeGroupVersion.WithKind(kind).GroupKind()
}
// Resource takes an unqualified resource and returns a Group qualified GroupResource
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}
var (
// SchemeBuilder collects functions that add things to a scheme. It's to allow
// code to compile without explicitly referencing generated types. You should
// declare one in each package that will have generated deep copy or conversion
// functions.
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
// AddToScheme applies all the stored functions to the scheme. A non-nil error
// indicates that one function failed and the attempt was abandoned.
AddToScheme = SchemeBuilder.AddToScheme
)
// Adds the list of known types to Scheme.
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&ServiceProfile{},
&ServiceProfileList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
}
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1alpha2
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Range) DeepCopyInto(out *Range) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Range.
func (in *Range) DeepCopy() *Range {
if in == nil {
return nil
}
out := new(Range)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RequestMatch) DeepCopyInto(out *RequestMatch) {
*out = *in
if in.All != nil {
in, out := &in.All, &out.All
*out = make([]*RequestMatch, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(RequestMatch)
(*in).DeepCopyInto(*out)
}
}
}
if in.Not != nil {
in, out := &in.Not, &out.Not
*out = new(RequestMatch)
(*in).DeepCopyInto(*out)
}
if in.Any != nil {
in, out := &in.Any, &out.Any
*out = make([]*RequestMatch, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(RequestMatch)
(*in).DeepCopyInto(*out)
}
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RequestMatch.
func (in *RequestMatch) DeepCopy() *RequestMatch {
if in == nil {
return nil
}
out := new(RequestMatch)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ResponseClass) DeepCopyInto(out *ResponseClass) {
*out = *in
if in.Condition != nil {
in, out := &in.Condition, &out.Condition
*out = new(ResponseMatch)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResponseClass.
func (in *ResponseClass) DeepCopy() *ResponseClass {
if in == nil {
return nil
}
out := new(ResponseClass)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ResponseMatch) DeepCopyInto(out *ResponseMatch) {
*out = *in
if in.All != nil {
in, out := &in.All, &out.All
*out = make([]*ResponseMatch, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(ResponseMatch)
(*in).DeepCopyInto(*out)
}
}
}
if in.Not != nil {
in, out := &in.Not, &out.Not
*out = new(ResponseMatch)
(*in).DeepCopyInto(*out)
}
if in.Any != nil {
in, out := &in.Any, &out.Any
*out = make([]*ResponseMatch, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(ResponseMatch)
(*in).DeepCopyInto(*out)
}
}
}
if in.Status != nil {
in, out := &in.Status, &out.Status
*out = new(Range)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResponseMatch.
func (in *ResponseMatch) DeepCopy() *ResponseMatch {
if in == nil {
return nil
}
out := new(ResponseMatch)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RetryBudget) DeepCopyInto(out *RetryBudget) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RetryBudget.
func (in *RetryBudget) DeepCopy() *RetryBudget {
if in == nil {
return nil
}
out := new(RetryBudget)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RouteSpec) DeepCopyInto(out *RouteSpec) {
*out = *in
if in.Condition != nil {
in, out := &in.Condition, &out.Condition
*out = new(RequestMatch)
(*in).DeepCopyInto(*out)
}
if in.ResponseClasses != nil {
in, out := &in.ResponseClasses, &out.ResponseClasses
*out = make([]*ResponseClass, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(ResponseClass)
(*in).DeepCopyInto(*out)
}
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteSpec.
func (in *RouteSpec) DeepCopy() *RouteSpec {
if in == nil {
return nil
}
out := new(RouteSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceProfile) DeepCopyInto(out *ServiceProfile) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceProfile.
func (in *ServiceProfile) DeepCopy() *ServiceProfile {
if in == nil {
return nil
}
out := new(ServiceProfile)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ServiceProfile) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceProfileList) DeepCopyInto(out *ServiceProfileList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]ServiceProfile, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceProfileList.
func (in *ServiceProfileList) DeepCopy() *ServiceProfileList {
if in == nil {
return nil
}
out := new(ServiceProfileList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ServiceProfileList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceProfileSpec) DeepCopyInto(out *ServiceProfileSpec) {
*out = *in
if in.Routes != nil {
in, out := &in.Routes, &out.Routes
*out = make([]*RouteSpec, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(RouteSpec)
(*in).DeepCopyInto(*out)
}
}
}
if in.RetryBudget != nil {
in, out := &in.RetryBudget, &out.RetryBudget
*out = new(RetryBudget)
**out = **in
}
if in.DstOverrides != nil {
in, out := &in.DstOverrides, &out.DstOverrides
*out = make([]*WeightedDst, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(WeightedDst)
(*in).DeepCopyInto(*out)
}
}
}
if in.OpaquePorts != nil {
in, out := &in.OpaquePorts, &out.OpaquePorts
*out = make(map[uint32]struct{}, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceProfileSpec.
func (in *ServiceProfileSpec) DeepCopy() *ServiceProfileSpec {
if in == nil {
return nil
}
out := new(ServiceProfileSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *WeightedDst) DeepCopyInto(out *WeightedDst) {
*out = *in
out.Weight = in.Weight.DeepCopy()
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WeightedDst.
func (in *WeightedDst) DeepCopy() *WeightedDst {
if in == nil {
return nil
}
out := new(WeightedDst)
in.DeepCopyInto(out)
return out
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package versioned
import (
"fmt"
"net/http"
externalworkloadv1beta1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1"
linkv1alpha1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/link/v1alpha1"
policyv1alpha1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/policy/v1alpha1"
policyv1beta3 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/policy/v1beta3"
serverv1beta1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/server/v1beta1"
serverv1beta2 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/server/v1beta2"
serverauthorizationv1beta1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/serverauthorization/v1beta1"
linkerdv1alpha2 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/serviceprofile/v1alpha2"
discovery "k8s.io/client-go/discovery"
rest "k8s.io/client-go/rest"
flowcontrol "k8s.io/client-go/util/flowcontrol"
)
type Interface interface {
Discovery() discovery.DiscoveryInterface
ExternalworkloadV1beta1() externalworkloadv1beta1.ExternalworkloadV1beta1Interface
LinkV1alpha1() linkv1alpha1.LinkV1alpha1Interface
PolicyV1alpha1() policyv1alpha1.PolicyV1alpha1Interface
PolicyV1beta3() policyv1beta3.PolicyV1beta3Interface
ServerV1beta1() serverv1beta1.ServerV1beta1Interface
ServerV1beta2() serverv1beta2.ServerV1beta2Interface
ServerauthorizationV1beta1() serverauthorizationv1beta1.ServerauthorizationV1beta1Interface
LinkerdV1alpha2() linkerdv1alpha2.LinkerdV1alpha2Interface
}
// Clientset contains the clients for groups.
type Clientset struct {
*discovery.DiscoveryClient
externalworkloadV1beta1 *externalworkloadv1beta1.ExternalworkloadV1beta1Client
linkV1alpha1 *linkv1alpha1.LinkV1alpha1Client
policyV1alpha1 *policyv1alpha1.PolicyV1alpha1Client
policyV1beta3 *policyv1beta3.PolicyV1beta3Client
serverV1beta1 *serverv1beta1.ServerV1beta1Client
serverV1beta2 *serverv1beta2.ServerV1beta2Client
serverauthorizationV1beta1 *serverauthorizationv1beta1.ServerauthorizationV1beta1Client
linkerdV1alpha2 *linkerdv1alpha2.LinkerdV1alpha2Client
}
// ExternalworkloadV1beta1 retrieves the ExternalworkloadV1beta1Client
func (c *Clientset) ExternalworkloadV1beta1() externalworkloadv1beta1.ExternalworkloadV1beta1Interface {
return c.externalworkloadV1beta1
}
// LinkV1alpha1 retrieves the LinkV1alpha1Client
func (c *Clientset) LinkV1alpha1() linkv1alpha1.LinkV1alpha1Interface {
return c.linkV1alpha1
}
// PolicyV1alpha1 retrieves the PolicyV1alpha1Client
func (c *Clientset) PolicyV1alpha1() policyv1alpha1.PolicyV1alpha1Interface {
return c.policyV1alpha1
}
// PolicyV1beta3 retrieves the PolicyV1beta3Client
func (c *Clientset) PolicyV1beta3() policyv1beta3.PolicyV1beta3Interface {
return c.policyV1beta3
}
// ServerV1beta1 retrieves the ServerV1beta1Client
func (c *Clientset) ServerV1beta1() serverv1beta1.ServerV1beta1Interface {
return c.serverV1beta1
}
// ServerV1beta2 retrieves the ServerV1beta2Client
func (c *Clientset) ServerV1beta2() serverv1beta2.ServerV1beta2Interface {
return c.serverV1beta2
}
// ServerauthorizationV1beta1 retrieves the ServerauthorizationV1beta1Client
func (c *Clientset) ServerauthorizationV1beta1() serverauthorizationv1beta1.ServerauthorizationV1beta1Interface {
return c.serverauthorizationV1beta1
}
// LinkerdV1alpha2 retrieves the LinkerdV1alpha2Client
func (c *Clientset) LinkerdV1alpha2() linkerdv1alpha2.LinkerdV1alpha2Interface {
return c.linkerdV1alpha2
}
// Discovery retrieves the DiscoveryClient
func (c *Clientset) Discovery() discovery.DiscoveryInterface {
if c == nil {
return nil
}
return c.DiscoveryClient
}
// NewForConfig creates a new Clientset for the given config.
// If config's RateLimiter is not set and QPS and Burst are acceptable,
// NewForConfig will generate a rate-limiter in configShallowCopy.
// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
// where httpClient was generated with rest.HTTPClientFor(c).
func NewForConfig(c *rest.Config) (*Clientset, error) {
configShallowCopy := *c
if configShallowCopy.UserAgent == "" {
configShallowCopy.UserAgent = rest.DefaultKubernetesUserAgent()
}
// share the transport between all clients
httpClient, err := rest.HTTPClientFor(&configShallowCopy)
if err != nil {
return nil, err
}
return NewForConfigAndClient(&configShallowCopy, httpClient)
}
// NewForConfigAndClient creates a new Clientset for the given config and http client.
// Note the http client provided takes precedence over the configured transport values.
// If config's RateLimiter is not set and QPS and Burst are acceptable,
// NewForConfigAndClient will generate a rate-limiter in configShallowCopy.
func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, error) {
configShallowCopy := *c
if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 {
if configShallowCopy.Burst <= 0 {
return nil, fmt.Errorf("burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0")
}
configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst)
}
var cs Clientset
var err error
cs.externalworkloadV1beta1, err = externalworkloadv1beta1.NewForConfigAndClient(&configShallowCopy, httpClient)
if err != nil {
return nil, err
}
cs.linkV1alpha1, err = linkv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient)
if err != nil {
return nil, err
}
cs.policyV1alpha1, err = policyv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient)
if err != nil {
return nil, err
}
cs.policyV1beta3, err = policyv1beta3.NewForConfigAndClient(&configShallowCopy, httpClient)
if err != nil {
return nil, err
}
cs.serverV1beta1, err = serverv1beta1.NewForConfigAndClient(&configShallowCopy, httpClient)
if err != nil {
return nil, err
}
cs.serverV1beta2, err = serverv1beta2.NewForConfigAndClient(&configShallowCopy, httpClient)
if err != nil {
return nil, err
}
cs.serverauthorizationV1beta1, err = serverauthorizationv1beta1.NewForConfigAndClient(&configShallowCopy, httpClient)
if err != nil {
return nil, err
}
cs.linkerdV1alpha2, err = linkerdv1alpha2.NewForConfigAndClient(&configShallowCopy, httpClient)
if err != nil {
return nil, err
}
cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfigAndClient(&configShallowCopy, httpClient)
if err != nil {
return nil, err
}
return &cs, nil
}
// NewForConfigOrDie creates a new Clientset for the given config and
// panics if there is an error in the config.
func NewForConfigOrDie(c *rest.Config) *Clientset {
cs, err := NewForConfig(c)
if err != nil {
panic(err)
}
return cs
}
// New creates a new Clientset for the given RESTClient.
func New(c rest.Interface) *Clientset {
var cs Clientset
cs.externalworkloadV1beta1 = externalworkloadv1beta1.New(c)
cs.linkV1alpha1 = linkv1alpha1.New(c)
cs.policyV1alpha1 = policyv1alpha1.New(c)
cs.policyV1beta3 = policyv1beta3.New(c)
cs.serverV1beta1 = serverv1beta1.New(c)
cs.serverV1beta2 = serverv1beta2.New(c)
cs.serverauthorizationV1beta1 = serverauthorizationv1beta1.New(c)
cs.linkerdV1alpha2 = linkerdv1alpha2.New(c)
cs.DiscoveryClient = discovery.NewDiscoveryClient(c)
return &cs
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
clientset "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned"
externalworkloadv1beta1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1"
fakeexternalworkloadv1beta1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1/fake"
linkv1alpha1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/link/v1alpha1"
fakelinkv1alpha1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/link/v1alpha1/fake"
policyv1alpha1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/policy/v1alpha1"
fakepolicyv1alpha1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/policy/v1alpha1/fake"
policyv1beta3 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/policy/v1beta3"
fakepolicyv1beta3 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/policy/v1beta3/fake"
serverv1beta1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/server/v1beta1"
fakeserverv1beta1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/server/v1beta1/fake"
serverv1beta2 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/server/v1beta2"
fakeserverv1beta2 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/server/v1beta2/fake"
serverauthorizationv1beta1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/serverauthorization/v1beta1"
fakeserverauthorizationv1beta1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/serverauthorization/v1beta1/fake"
linkerdv1alpha2 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/serviceprofile/v1alpha2"
fakelinkerdv1alpha2 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/serviceprofile/v1alpha2/fake"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/discovery"
fakediscovery "k8s.io/client-go/discovery/fake"
"k8s.io/client-go/testing"
)
// NewSimpleClientset returns a clientset that will respond with the provided objects.
// It's backed by a very simple object tracker that processes creates, updates and deletions as-is,
// without applying any validations and/or defaults. It shouldn't be considered a replacement
// for a real clientset and is mostly useful in simple unit tests.
func NewSimpleClientset(objects ...runtime.Object) *Clientset {
o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder())
for _, obj := range objects {
if err := o.Add(obj); err != nil {
panic(err)
}
}
cs := &Clientset{tracker: o}
cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake}
cs.AddReactor("*", "*", testing.ObjectReaction(o))
cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) {
gvr := action.GetResource()
ns := action.GetNamespace()
watch, err := o.Watch(gvr, ns)
if err != nil {
return false, nil, err
}
return true, watch, nil
})
return cs
}
// Clientset implements clientset.Interface. Meant to be embedded into a
// struct to get a default implementation. This makes faking out just the method
// you want to test easier.
type Clientset struct {
testing.Fake
discovery *fakediscovery.FakeDiscovery
tracker testing.ObjectTracker
}
func (c *Clientset) Discovery() discovery.DiscoveryInterface {
return c.discovery
}
func (c *Clientset) Tracker() testing.ObjectTracker {
return c.tracker
}
var (
_ clientset.Interface = &Clientset{}
_ testing.FakeClient = &Clientset{}
)
// ExternalworkloadV1beta1 retrieves the ExternalworkloadV1beta1Client
func (c *Clientset) ExternalworkloadV1beta1() externalworkloadv1beta1.ExternalworkloadV1beta1Interface {
return &fakeexternalworkloadv1beta1.FakeExternalworkloadV1beta1{Fake: &c.Fake}
}
// LinkV1alpha1 retrieves the LinkV1alpha1Client
func (c *Clientset) LinkV1alpha1() linkv1alpha1.LinkV1alpha1Interface {
return &fakelinkv1alpha1.FakeLinkV1alpha1{Fake: &c.Fake}
}
// PolicyV1alpha1 retrieves the PolicyV1alpha1Client
func (c *Clientset) PolicyV1alpha1() policyv1alpha1.PolicyV1alpha1Interface {
return &fakepolicyv1alpha1.FakePolicyV1alpha1{Fake: &c.Fake}
}
// PolicyV1beta3 retrieves the PolicyV1beta3Client
func (c *Clientset) PolicyV1beta3() policyv1beta3.PolicyV1beta3Interface {
return &fakepolicyv1beta3.FakePolicyV1beta3{Fake: &c.Fake}
}
// ServerV1beta1 retrieves the ServerV1beta1Client
func (c *Clientset) ServerV1beta1() serverv1beta1.ServerV1beta1Interface {
return &fakeserverv1beta1.FakeServerV1beta1{Fake: &c.Fake}
}
// ServerV1beta2 retrieves the ServerV1beta2Client
func (c *Clientset) ServerV1beta2() serverv1beta2.ServerV1beta2Interface {
return &fakeserverv1beta2.FakeServerV1beta2{Fake: &c.Fake}
}
// ServerauthorizationV1beta1 retrieves the ServerauthorizationV1beta1Client
func (c *Clientset) ServerauthorizationV1beta1() serverauthorizationv1beta1.ServerauthorizationV1beta1Interface {
return &fakeserverauthorizationv1beta1.FakeServerauthorizationV1beta1{Fake: &c.Fake}
}
// LinkerdV1alpha2 retrieves the LinkerdV1alpha2Client
func (c *Clientset) LinkerdV1alpha2() linkerdv1alpha2.LinkerdV1alpha2Interface {
return &fakelinkerdv1alpha2.FakeLinkerdV1alpha2{Fake: &c.Fake}
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
externalworkloadv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1"
linkv1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha1"
policyv1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1"
policyv1beta3 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1beta3"
serverv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta1"
serverv1beta2 "github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta2"
serverauthorizationv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/serverauthorization/v1beta1"
linkerdv1alpha2 "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
schema "k8s.io/apimachinery/pkg/runtime/schema"
serializer "k8s.io/apimachinery/pkg/runtime/serializer"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
)
var scheme = runtime.NewScheme()
var codecs = serializer.NewCodecFactory(scheme)
var localSchemeBuilder = runtime.SchemeBuilder{
externalworkloadv1beta1.AddToScheme,
linkv1alpha1.AddToScheme,
policyv1alpha1.AddToScheme,
policyv1beta3.AddToScheme,
serverv1beta1.AddToScheme,
serverv1beta2.AddToScheme,
serverauthorizationv1beta1.AddToScheme,
linkerdv1alpha2.AddToScheme,
}
// AddToScheme adds all types of this clientset into the given scheme. This allows composition
// of clientsets, like in:
//
// import (
// "k8s.io/client-go/kubernetes"
// clientsetscheme "k8s.io/client-go/kubernetes/scheme"
// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
// )
//
// kclientset, _ := kubernetes.NewForConfig(c)
// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
//
// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
// correctly.
var AddToScheme = localSchemeBuilder.AddToScheme
func init() {
v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"})
utilruntime.Must(AddToScheme(scheme))
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package scheme
import (
externalworkloadv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1"
linkv1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha1"
policyv1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1"
policyv1beta3 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1beta3"
serverv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta1"
serverv1beta2 "github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta2"
serverauthorizationv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/serverauthorization/v1beta1"
linkerdv1alpha2 "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
schema "k8s.io/apimachinery/pkg/runtime/schema"
serializer "k8s.io/apimachinery/pkg/runtime/serializer"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
)
var Scheme = runtime.NewScheme()
var Codecs = serializer.NewCodecFactory(Scheme)
var ParameterCodec = runtime.NewParameterCodec(Scheme)
var localSchemeBuilder = runtime.SchemeBuilder{
externalworkloadv1beta1.AddToScheme,
linkv1alpha1.AddToScheme,
policyv1alpha1.AddToScheme,
policyv1beta3.AddToScheme,
serverv1beta1.AddToScheme,
serverv1beta2.AddToScheme,
serverauthorizationv1beta1.AddToScheme,
linkerdv1alpha2.AddToScheme,
}
// AddToScheme adds all types of this clientset into the given scheme. This allows composition
// of clientsets, like in:
//
// import (
// "k8s.io/client-go/kubernetes"
// clientsetscheme "k8s.io/client-go/kubernetes/scheme"
// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
// )
//
// kclientset, _ := kubernetes.NewForConfig(c)
// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
//
// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
// correctly.
var AddToScheme = localSchemeBuilder.AddToScheme
func init() {
v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"})
utilruntime.Must(AddToScheme(Scheme))
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package v1beta1
import (
"context"
"time"
v1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1"
scheme "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
rest "k8s.io/client-go/rest"
)
// ExternalWorkloadsGetter has a method to return a ExternalWorkloadInterface.
// A group's client should implement this interface.
type ExternalWorkloadsGetter interface {
ExternalWorkloads(namespace string) ExternalWorkloadInterface
}
// ExternalWorkloadInterface has methods to work with ExternalWorkload resources.
type ExternalWorkloadInterface interface {
Create(ctx context.Context, externalWorkload *v1beta1.ExternalWorkload, opts v1.CreateOptions) (*v1beta1.ExternalWorkload, error)
Update(ctx context.Context, externalWorkload *v1beta1.ExternalWorkload, opts v1.UpdateOptions) (*v1beta1.ExternalWorkload, 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) (*v1beta1.ExternalWorkload, error)
List(ctx context.Context, opts v1.ListOptions) (*v1beta1.ExternalWorkloadList, error)
Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.ExternalWorkload, err error)
ExternalWorkloadExpansion
}
// externalWorkloads implements ExternalWorkloadInterface
type externalWorkloads struct {
client rest.Interface
ns string
}
// newExternalWorkloads returns a ExternalWorkloads
func newExternalWorkloads(c *ExternalworkloadV1beta1Client, namespace string) *externalWorkloads {
return &externalWorkloads{
client: c.RESTClient(),
ns: namespace,
}
}
// Get takes name of the externalWorkload, and returns the corresponding externalWorkload object, and an error if there is any.
func (c *externalWorkloads) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.ExternalWorkload, err error) {
result = &v1beta1.ExternalWorkload{}
err = c.client.Get().
Namespace(c.ns).
Resource("externalworkloads").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do(ctx).
Into(result)
return
}
// List takes label and field selectors, and returns the list of ExternalWorkloads that match those selectors.
func (c *externalWorkloads) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.ExternalWorkloadList, err error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
result = &v1beta1.ExternalWorkloadList{}
err = c.client.Get().
Namespace(c.ns).
Resource("externalworkloads").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Do(ctx).
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested externalWorkloads.
func (c *externalWorkloads) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
opts.Watch = true
return c.client.Get().
Namespace(c.ns).
Resource("externalworkloads").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Watch(ctx)
}
// Create takes the representation of a externalWorkload and creates it. Returns the server's representation of the externalWorkload, and an error, if there is any.
func (c *externalWorkloads) Create(ctx context.Context, externalWorkload *v1beta1.ExternalWorkload, opts v1.CreateOptions) (result *v1beta1.ExternalWorkload, err error) {
result = &v1beta1.ExternalWorkload{}
err = c.client.Post().
Namespace(c.ns).
Resource("externalworkloads").
VersionedParams(&opts, scheme.ParameterCodec).
Body(externalWorkload).
Do(ctx).
Into(result)
return
}
// Update takes the representation of a externalWorkload and updates it. Returns the server's representation of the externalWorkload, and an error, if there is any.
func (c *externalWorkloads) Update(ctx context.Context, externalWorkload *v1beta1.ExternalWorkload, opts v1.UpdateOptions) (result *v1beta1.ExternalWorkload, err error) {
result = &v1beta1.ExternalWorkload{}
err = c.client.Put().
Namespace(c.ns).
Resource("externalworkloads").
Name(externalWorkload.Name).
VersionedParams(&opts, scheme.ParameterCodec).
Body(externalWorkload).
Do(ctx).
Into(result)
return
}
// Delete takes name of the externalWorkload and deletes it. Returns an error if one occurs.
func (c *externalWorkloads) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
return c.client.Delete().
Namespace(c.ns).
Resource("externalworkloads").
Name(name).
Body(&opts).
Do(ctx).
Error()
}
// DeleteCollection deletes a collection of objects.
func (c *externalWorkloads) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
var timeout time.Duration
if listOpts.TimeoutSeconds != nil {
timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second
}
return c.client.Delete().
Namespace(c.ns).
Resource("externalworkloads").
VersionedParams(&listOpts, scheme.ParameterCodec).
Timeout(timeout).
Body(&opts).
Do(ctx).
Error()
}
// Patch applies the patch and returns the patched externalWorkload.
func (c *externalWorkloads) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.ExternalWorkload, err error) {
result = &v1beta1.ExternalWorkload{}
err = c.client.Patch(pt).
Namespace(c.ns).
Resource("externalworkloads").
Name(name).
SubResource(subresources...).
VersionedParams(&opts, scheme.ParameterCodec).
Body(data).
Do(ctx).
Into(result)
return
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package v1beta1
import (
"net/http"
v1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1"
"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme"
rest "k8s.io/client-go/rest"
)
type ExternalworkloadV1beta1Interface interface {
RESTClient() rest.Interface
ExternalWorkloadsGetter
}
// ExternalworkloadV1beta1Client is used to interact with features provided by the externalworkload group.
type ExternalworkloadV1beta1Client struct {
restClient rest.Interface
}
func (c *ExternalworkloadV1beta1Client) ExternalWorkloads(namespace string) ExternalWorkloadInterface {
return newExternalWorkloads(c, namespace)
}
// NewForConfig creates a new ExternalworkloadV1beta1Client for the given config.
// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
// where httpClient was generated with rest.HTTPClientFor(c).
func NewForConfig(c *rest.Config) (*ExternalworkloadV1beta1Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
httpClient, err := rest.HTTPClientFor(&config)
if err != nil {
return nil, err
}
return NewForConfigAndClient(&config, httpClient)
}
// NewForConfigAndClient creates a new ExternalworkloadV1beta1Client for the given config and http client.
// Note the http client provided takes precedence over the configured transport values.
func NewForConfigAndClient(c *rest.Config, h *http.Client) (*ExternalworkloadV1beta1Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
client, err := rest.RESTClientForConfigAndClient(&config, h)
if err != nil {
return nil, err
}
return &ExternalworkloadV1beta1Client{client}, nil
}
// NewForConfigOrDie creates a new ExternalworkloadV1beta1Client for the given config and
// panics if there is an error in the config.
func NewForConfigOrDie(c *rest.Config) *ExternalworkloadV1beta1Client {
client, err := NewForConfig(c)
if err != nil {
panic(err)
}
return client
}
// New creates a new ExternalworkloadV1beta1Client for the given RESTClient.
func New(c rest.Interface) *ExternalworkloadV1beta1Client {
return &ExternalworkloadV1beta1Client{c}
}
func setConfigDefaults(config *rest.Config) error {
gv := v1beta1.SchemeGroupVersion
config.GroupVersion = &gv
config.APIPath = "/apis"
config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
if config.UserAgent == "" {
config.UserAgent = rest.DefaultKubernetesUserAgent()
}
return nil
}
// RESTClient returns a RESTClient that is used to communicate
// with API server by this client implementation.
func (c *ExternalworkloadV1beta1Client) RESTClient() rest.Interface {
if c == nil {
return nil
}
return c.restClient
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
"context"
v1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
)
// FakeExternalWorkloads implements ExternalWorkloadInterface
type FakeExternalWorkloads struct {
Fake *FakeExternalworkloadV1beta1
ns string
}
var externalworkloadsResource = v1beta1.SchemeGroupVersion.WithResource("externalworkloads")
var externalworkloadsKind = v1beta1.SchemeGroupVersion.WithKind("ExternalWorkload")
// Get takes name of the externalWorkload, and returns the corresponding externalWorkload object, and an error if there is any.
func (c *FakeExternalWorkloads) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.ExternalWorkload, err error) {
obj, err := c.Fake.
Invokes(testing.NewGetAction(externalworkloadsResource, c.ns, name), &v1beta1.ExternalWorkload{})
if obj == nil {
return nil, err
}
return obj.(*v1beta1.ExternalWorkload), err
}
// List takes label and field selectors, and returns the list of ExternalWorkloads that match those selectors.
func (c *FakeExternalWorkloads) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.ExternalWorkloadList, err error) {
obj, err := c.Fake.
Invokes(testing.NewListAction(externalworkloadsResource, externalworkloadsKind, c.ns, opts), &v1beta1.ExternalWorkloadList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &v1beta1.ExternalWorkloadList{ListMeta: obj.(*v1beta1.ExternalWorkloadList).ListMeta}
for _, item := range obj.(*v1beta1.ExternalWorkloadList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested externalWorkloads.
func (c *FakeExternalWorkloads) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewWatchAction(externalworkloadsResource, c.ns, opts))
}
// Create takes the representation of a externalWorkload and creates it. Returns the server's representation of the externalWorkload, and an error, if there is any.
func (c *FakeExternalWorkloads) Create(ctx context.Context, externalWorkload *v1beta1.ExternalWorkload, opts v1.CreateOptions) (result *v1beta1.ExternalWorkload, err error) {
obj, err := c.Fake.
Invokes(testing.NewCreateAction(externalworkloadsResource, c.ns, externalWorkload), &v1beta1.ExternalWorkload{})
if obj == nil {
return nil, err
}
return obj.(*v1beta1.ExternalWorkload), err
}
// Update takes the representation of a externalWorkload and updates it. Returns the server's representation of the externalWorkload, and an error, if there is any.
func (c *FakeExternalWorkloads) Update(ctx context.Context, externalWorkload *v1beta1.ExternalWorkload, opts v1.UpdateOptions) (result *v1beta1.ExternalWorkload, err error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateAction(externalworkloadsResource, c.ns, externalWorkload), &v1beta1.ExternalWorkload{})
if obj == nil {
return nil, err
}
return obj.(*v1beta1.ExternalWorkload), err
}
// Delete takes name of the externalWorkload and deletes it. Returns an error if one occurs.
func (c *FakeExternalWorkloads) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewDeleteActionWithOptions(externalworkloadsResource, c.ns, name, opts), &v1beta1.ExternalWorkload{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeExternalWorkloads) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
action := testing.NewDeleteCollectionAction(externalworkloadsResource, c.ns, listOpts)
_, err := c.Fake.Invokes(action, &v1beta1.ExternalWorkloadList{})
return err
}
// Patch applies the patch and returns the patched externalWorkload.
func (c *FakeExternalWorkloads) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.ExternalWorkload, err error) {
obj, err := c.Fake.
Invokes(testing.NewPatchSubresourceAction(externalworkloadsResource, c.ns, name, pt, data, subresources...), &v1beta1.ExternalWorkload{})
if obj == nil {
return nil, err
}
return obj.(*v1beta1.ExternalWorkload), err
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
v1beta1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1"
rest "k8s.io/client-go/rest"
testing "k8s.io/client-go/testing"
)
type FakeExternalworkloadV1beta1 struct {
*testing.Fake
}
func (c *FakeExternalworkloadV1beta1) ExternalWorkloads(namespace string) v1beta1.ExternalWorkloadInterface {
return &FakeExternalWorkloads{c, namespace}
}
// RESTClient returns a RESTClient that is used to communicate
// with API server by this client implementation.
func (c *FakeExternalworkloadV1beta1) RESTClient() rest.Interface {
var ret *rest.RESTClient
return ret
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
"context"
v1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
)
// FakeLinks implements LinkInterface
type FakeLinks struct {
Fake *FakeLinkV1alpha1
ns string
}
var linksResource = v1alpha1.SchemeGroupVersion.WithResource("links")
var linksKind = v1alpha1.SchemeGroupVersion.WithKind("Link")
// Get takes name of the link, and returns the corresponding link object, and an error if there is any.
func (c *FakeLinks) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.Link, err error) {
obj, err := c.Fake.
Invokes(testing.NewGetAction(linksResource, c.ns, name), &v1alpha1.Link{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.Link), err
}
// List takes label and field selectors, and returns the list of Links that match those selectors.
func (c *FakeLinks) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.LinkList, err error) {
obj, err := c.Fake.
Invokes(testing.NewListAction(linksResource, linksKind, c.ns, opts), &v1alpha1.LinkList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &v1alpha1.LinkList{ListMeta: obj.(*v1alpha1.LinkList).ListMeta}
for _, item := range obj.(*v1alpha1.LinkList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested links.
func (c *FakeLinks) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewWatchAction(linksResource, c.ns, opts))
}
// Create takes the representation of a link and creates it. Returns the server's representation of the link, and an error, if there is any.
func (c *FakeLinks) Create(ctx context.Context, link *v1alpha1.Link, opts v1.CreateOptions) (result *v1alpha1.Link, err error) {
obj, err := c.Fake.
Invokes(testing.NewCreateAction(linksResource, c.ns, link), &v1alpha1.Link{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.Link), err
}
// Update takes the representation of a link and updates it. Returns the server's representation of the link, and an error, if there is any.
func (c *FakeLinks) Update(ctx context.Context, link *v1alpha1.Link, opts v1.UpdateOptions) (result *v1alpha1.Link, err error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateAction(linksResource, c.ns, link), &v1alpha1.Link{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.Link), err
}
// Delete takes name of the link and deletes it. Returns an error if one occurs.
func (c *FakeLinks) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewDeleteActionWithOptions(linksResource, c.ns, name, opts), &v1alpha1.Link{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeLinks) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
action := testing.NewDeleteCollectionAction(linksResource, c.ns, listOpts)
_, err := c.Fake.Invokes(action, &v1alpha1.LinkList{})
return err
}
// Patch applies the patch and returns the patched link.
func (c *FakeLinks) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.Link, err error) {
obj, err := c.Fake.
Invokes(testing.NewPatchSubresourceAction(linksResource, c.ns, name, pt, data, subresources...), &v1alpha1.Link{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.Link), err
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
v1alpha1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/link/v1alpha1"
rest "k8s.io/client-go/rest"
testing "k8s.io/client-go/testing"
)
type FakeLinkV1alpha1 struct {
*testing.Fake
}
func (c *FakeLinkV1alpha1) Links(namespace string) v1alpha1.LinkInterface {
return &FakeLinks{c, namespace}
}
// RESTClient returns a RESTClient that is used to communicate
// with API server by this client implementation.
func (c *FakeLinkV1alpha1) RESTClient() rest.Interface {
var ret *rest.RESTClient
return ret
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package v1alpha1
import (
"context"
"time"
v1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha1"
scheme "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
rest "k8s.io/client-go/rest"
)
// LinksGetter has a method to return a LinkInterface.
// A group's client should implement this interface.
type LinksGetter interface {
Links(namespace string) LinkInterface
}
// LinkInterface has methods to work with Link resources.
type LinkInterface interface {
Create(ctx context.Context, link *v1alpha1.Link, opts v1.CreateOptions) (*v1alpha1.Link, error)
Update(ctx context.Context, link *v1alpha1.Link, opts v1.UpdateOptions) (*v1alpha1.Link, 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) (*v1alpha1.Link, error)
List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.LinkList, error)
Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.Link, err error)
LinkExpansion
}
// links implements LinkInterface
type links struct {
client rest.Interface
ns string
}
// newLinks returns a Links
func newLinks(c *LinkV1alpha1Client, namespace string) *links {
return &links{
client: c.RESTClient(),
ns: namespace,
}
}
// Get takes name of the link, and returns the corresponding link object, and an error if there is any.
func (c *links) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.Link, err error) {
result = &v1alpha1.Link{}
err = c.client.Get().
Namespace(c.ns).
Resource("links").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do(ctx).
Into(result)
return
}
// List takes label and field selectors, and returns the list of Links that match those selectors.
func (c *links) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.LinkList, err error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
result = &v1alpha1.LinkList{}
err = c.client.Get().
Namespace(c.ns).
Resource("links").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Do(ctx).
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested links.
func (c *links) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
opts.Watch = true
return c.client.Get().
Namespace(c.ns).
Resource("links").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Watch(ctx)
}
// Create takes the representation of a link and creates it. Returns the server's representation of the link, and an error, if there is any.
func (c *links) Create(ctx context.Context, link *v1alpha1.Link, opts v1.CreateOptions) (result *v1alpha1.Link, err error) {
result = &v1alpha1.Link{}
err = c.client.Post().
Namespace(c.ns).
Resource("links").
VersionedParams(&opts, scheme.ParameterCodec).
Body(link).
Do(ctx).
Into(result)
return
}
// Update takes the representation of a link and updates it. Returns the server's representation of the link, and an error, if there is any.
func (c *links) Update(ctx context.Context, link *v1alpha1.Link, opts v1.UpdateOptions) (result *v1alpha1.Link, err error) {
result = &v1alpha1.Link{}
err = c.client.Put().
Namespace(c.ns).
Resource("links").
Name(link.Name).
VersionedParams(&opts, scheme.ParameterCodec).
Body(link).
Do(ctx).
Into(result)
return
}
// Delete takes name of the link and deletes it. Returns an error if one occurs.
func (c *links) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
return c.client.Delete().
Namespace(c.ns).
Resource("links").
Name(name).
Body(&opts).
Do(ctx).
Error()
}
// DeleteCollection deletes a collection of objects.
func (c *links) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
var timeout time.Duration
if listOpts.TimeoutSeconds != nil {
timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second
}
return c.client.Delete().
Namespace(c.ns).
Resource("links").
VersionedParams(&listOpts, scheme.ParameterCodec).
Timeout(timeout).
Body(&opts).
Do(ctx).
Error()
}
// Patch applies the patch and returns the patched link.
func (c *links) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.Link, err error) {
result = &v1alpha1.Link{}
err = c.client.Patch(pt).
Namespace(c.ns).
Resource("links").
Name(name).
SubResource(subresources...).
VersionedParams(&opts, scheme.ParameterCodec).
Body(data).
Do(ctx).
Into(result)
return
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package v1alpha1
import (
"net/http"
v1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha1"
"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme"
rest "k8s.io/client-go/rest"
)
type LinkV1alpha1Interface interface {
RESTClient() rest.Interface
LinksGetter
}
// LinkV1alpha1Client is used to interact with features provided by the link group.
type LinkV1alpha1Client struct {
restClient rest.Interface
}
func (c *LinkV1alpha1Client) Links(namespace string) LinkInterface {
return newLinks(c, namespace)
}
// NewForConfig creates a new LinkV1alpha1Client for the given config.
// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
// where httpClient was generated with rest.HTTPClientFor(c).
func NewForConfig(c *rest.Config) (*LinkV1alpha1Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
httpClient, err := rest.HTTPClientFor(&config)
if err != nil {
return nil, err
}
return NewForConfigAndClient(&config, httpClient)
}
// NewForConfigAndClient creates a new LinkV1alpha1Client for the given config and http client.
// Note the http client provided takes precedence over the configured transport values.
func NewForConfigAndClient(c *rest.Config, h *http.Client) (*LinkV1alpha1Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
client, err := rest.RESTClientForConfigAndClient(&config, h)
if err != nil {
return nil, err
}
return &LinkV1alpha1Client{client}, nil
}
// NewForConfigOrDie creates a new LinkV1alpha1Client for the given config and
// panics if there is an error in the config.
func NewForConfigOrDie(c *rest.Config) *LinkV1alpha1Client {
client, err := NewForConfig(c)
if err != nil {
panic(err)
}
return client
}
// New creates a new LinkV1alpha1Client for the given RESTClient.
func New(c rest.Interface) *LinkV1alpha1Client {
return &LinkV1alpha1Client{c}
}
func setConfigDefaults(config *rest.Config) error {
gv := v1alpha1.SchemeGroupVersion
config.GroupVersion = &gv
config.APIPath = "/apis"
config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
if config.UserAgent == "" {
config.UserAgent = rest.DefaultKubernetesUserAgent()
}
return nil
}
// RESTClient returns a RESTClient that is used to communicate
// with API server by this client implementation.
func (c *LinkV1alpha1Client) RESTClient() rest.Interface {
if c == nil {
return nil
}
return c.restClient
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package v1alpha1
import (
"context"
"time"
v1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1"
scheme "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
rest "k8s.io/client-go/rest"
)
// AuthorizationPoliciesGetter has a method to return a AuthorizationPolicyInterface.
// A group's client should implement this interface.
type AuthorizationPoliciesGetter interface {
AuthorizationPolicies(namespace string) AuthorizationPolicyInterface
}
// AuthorizationPolicyInterface has methods to work with AuthorizationPolicy resources.
type AuthorizationPolicyInterface interface {
Create(ctx context.Context, authorizationPolicy *v1alpha1.AuthorizationPolicy, opts v1.CreateOptions) (*v1alpha1.AuthorizationPolicy, error)
Update(ctx context.Context, authorizationPolicy *v1alpha1.AuthorizationPolicy, opts v1.UpdateOptions) (*v1alpha1.AuthorizationPolicy, 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) (*v1alpha1.AuthorizationPolicy, error)
List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.AuthorizationPolicyList, error)
Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.AuthorizationPolicy, err error)
AuthorizationPolicyExpansion
}
// authorizationPolicies implements AuthorizationPolicyInterface
type authorizationPolicies struct {
client rest.Interface
ns string
}
// newAuthorizationPolicies returns a AuthorizationPolicies
func newAuthorizationPolicies(c *PolicyV1alpha1Client, namespace string) *authorizationPolicies {
return &authorizationPolicies{
client: c.RESTClient(),
ns: namespace,
}
}
// Get takes name of the authorizationPolicy, and returns the corresponding authorizationPolicy object, and an error if there is any.
func (c *authorizationPolicies) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.AuthorizationPolicy, err error) {
result = &v1alpha1.AuthorizationPolicy{}
err = c.client.Get().
Namespace(c.ns).
Resource("authorizationpolicies").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do(ctx).
Into(result)
return
}
// List takes label and field selectors, and returns the list of AuthorizationPolicies that match those selectors.
func (c *authorizationPolicies) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.AuthorizationPolicyList, err error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
result = &v1alpha1.AuthorizationPolicyList{}
err = c.client.Get().
Namespace(c.ns).
Resource("authorizationpolicies").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Do(ctx).
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested authorizationPolicies.
func (c *authorizationPolicies) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
opts.Watch = true
return c.client.Get().
Namespace(c.ns).
Resource("authorizationpolicies").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Watch(ctx)
}
// Create takes the representation of a authorizationPolicy and creates it. Returns the server's representation of the authorizationPolicy, and an error, if there is any.
func (c *authorizationPolicies) Create(ctx context.Context, authorizationPolicy *v1alpha1.AuthorizationPolicy, opts v1.CreateOptions) (result *v1alpha1.AuthorizationPolicy, err error) {
result = &v1alpha1.AuthorizationPolicy{}
err = c.client.Post().
Namespace(c.ns).
Resource("authorizationpolicies").
VersionedParams(&opts, scheme.ParameterCodec).
Body(authorizationPolicy).
Do(ctx).
Into(result)
return
}
// Update takes the representation of a authorizationPolicy and updates it. Returns the server's representation of the authorizationPolicy, and an error, if there is any.
func (c *authorizationPolicies) Update(ctx context.Context, authorizationPolicy *v1alpha1.AuthorizationPolicy, opts v1.UpdateOptions) (result *v1alpha1.AuthorizationPolicy, err error) {
result = &v1alpha1.AuthorizationPolicy{}
err = c.client.Put().
Namespace(c.ns).
Resource("authorizationpolicies").
Name(authorizationPolicy.Name).
VersionedParams(&opts, scheme.ParameterCodec).
Body(authorizationPolicy).
Do(ctx).
Into(result)
return
}
// Delete takes name of the authorizationPolicy and deletes it. Returns an error if one occurs.
func (c *authorizationPolicies) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
return c.client.Delete().
Namespace(c.ns).
Resource("authorizationpolicies").
Name(name).
Body(&opts).
Do(ctx).
Error()
}
// DeleteCollection deletes a collection of objects.
func (c *authorizationPolicies) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
var timeout time.Duration
if listOpts.TimeoutSeconds != nil {
timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second
}
return c.client.Delete().
Namespace(c.ns).
Resource("authorizationpolicies").
VersionedParams(&listOpts, scheme.ParameterCodec).
Timeout(timeout).
Body(&opts).
Do(ctx).
Error()
}
// Patch applies the patch and returns the patched authorizationPolicy.
func (c *authorizationPolicies) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.AuthorizationPolicy, err error) {
result = &v1alpha1.AuthorizationPolicy{}
err = c.client.Patch(pt).
Namespace(c.ns).
Resource("authorizationpolicies").
Name(name).
SubResource(subresources...).
VersionedParams(&opts, scheme.ParameterCodec).
Body(data).
Do(ctx).
Into(result)
return
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
"context"
v1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
)
// FakeAuthorizationPolicies implements AuthorizationPolicyInterface
type FakeAuthorizationPolicies struct {
Fake *FakePolicyV1alpha1
ns string
}
var authorizationpoliciesResource = v1alpha1.SchemeGroupVersion.WithResource("authorizationpolicies")
var authorizationpoliciesKind = v1alpha1.SchemeGroupVersion.WithKind("AuthorizationPolicy")
// Get takes name of the authorizationPolicy, and returns the corresponding authorizationPolicy object, and an error if there is any.
func (c *FakeAuthorizationPolicies) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.AuthorizationPolicy, err error) {
obj, err := c.Fake.
Invokes(testing.NewGetAction(authorizationpoliciesResource, c.ns, name), &v1alpha1.AuthorizationPolicy{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.AuthorizationPolicy), err
}
// List takes label and field selectors, and returns the list of AuthorizationPolicies that match those selectors.
func (c *FakeAuthorizationPolicies) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.AuthorizationPolicyList, err error) {
obj, err := c.Fake.
Invokes(testing.NewListAction(authorizationpoliciesResource, authorizationpoliciesKind, c.ns, opts), &v1alpha1.AuthorizationPolicyList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &v1alpha1.AuthorizationPolicyList{ListMeta: obj.(*v1alpha1.AuthorizationPolicyList).ListMeta}
for _, item := range obj.(*v1alpha1.AuthorizationPolicyList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested authorizationPolicies.
func (c *FakeAuthorizationPolicies) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewWatchAction(authorizationpoliciesResource, c.ns, opts))
}
// Create takes the representation of a authorizationPolicy and creates it. Returns the server's representation of the authorizationPolicy, and an error, if there is any.
func (c *FakeAuthorizationPolicies) Create(ctx context.Context, authorizationPolicy *v1alpha1.AuthorizationPolicy, opts v1.CreateOptions) (result *v1alpha1.AuthorizationPolicy, err error) {
obj, err := c.Fake.
Invokes(testing.NewCreateAction(authorizationpoliciesResource, c.ns, authorizationPolicy), &v1alpha1.AuthorizationPolicy{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.AuthorizationPolicy), err
}
// Update takes the representation of a authorizationPolicy and updates it. Returns the server's representation of the authorizationPolicy, and an error, if there is any.
func (c *FakeAuthorizationPolicies) Update(ctx context.Context, authorizationPolicy *v1alpha1.AuthorizationPolicy, opts v1.UpdateOptions) (result *v1alpha1.AuthorizationPolicy, err error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateAction(authorizationpoliciesResource, c.ns, authorizationPolicy), &v1alpha1.AuthorizationPolicy{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.AuthorizationPolicy), err
}
// Delete takes name of the authorizationPolicy and deletes it. Returns an error if one occurs.
func (c *FakeAuthorizationPolicies) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewDeleteActionWithOptions(authorizationpoliciesResource, c.ns, name, opts), &v1alpha1.AuthorizationPolicy{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeAuthorizationPolicies) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
action := testing.NewDeleteCollectionAction(authorizationpoliciesResource, c.ns, listOpts)
_, err := c.Fake.Invokes(action, &v1alpha1.AuthorizationPolicyList{})
return err
}
// Patch applies the patch and returns the patched authorizationPolicy.
func (c *FakeAuthorizationPolicies) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.AuthorizationPolicy, err error) {
obj, err := c.Fake.
Invokes(testing.NewPatchSubresourceAction(authorizationpoliciesResource, c.ns, name, pt, data, subresources...), &v1alpha1.AuthorizationPolicy{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.AuthorizationPolicy), err
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
"context"
v1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
)
// FakeHTTPRoutes implements HTTPRouteInterface
type FakeHTTPRoutes struct {
Fake *FakePolicyV1alpha1
ns string
}
var httproutesResource = v1alpha1.SchemeGroupVersion.WithResource("httproutes")
var httproutesKind = v1alpha1.SchemeGroupVersion.WithKind("HTTPRoute")
// Get takes name of the hTTPRoute, and returns the corresponding hTTPRoute object, and an error if there is any.
func (c *FakeHTTPRoutes) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.HTTPRoute, err error) {
obj, err := c.Fake.
Invokes(testing.NewGetAction(httproutesResource, c.ns, name), &v1alpha1.HTTPRoute{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.HTTPRoute), err
}
// List takes label and field selectors, and returns the list of HTTPRoutes that match those selectors.
func (c *FakeHTTPRoutes) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.HTTPRouteList, err error) {
obj, err := c.Fake.
Invokes(testing.NewListAction(httproutesResource, httproutesKind, c.ns, opts), &v1alpha1.HTTPRouteList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &v1alpha1.HTTPRouteList{ListMeta: obj.(*v1alpha1.HTTPRouteList).ListMeta}
for _, item := range obj.(*v1alpha1.HTTPRouteList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested hTTPRoutes.
func (c *FakeHTTPRoutes) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewWatchAction(httproutesResource, c.ns, opts))
}
// Create takes the representation of a hTTPRoute and creates it. Returns the server's representation of the hTTPRoute, and an error, if there is any.
func (c *FakeHTTPRoutes) Create(ctx context.Context, hTTPRoute *v1alpha1.HTTPRoute, opts v1.CreateOptions) (result *v1alpha1.HTTPRoute, err error) {
obj, err := c.Fake.
Invokes(testing.NewCreateAction(httproutesResource, c.ns, hTTPRoute), &v1alpha1.HTTPRoute{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.HTTPRoute), err
}
// Update takes the representation of a hTTPRoute and updates it. Returns the server's representation of the hTTPRoute, and an error, if there is any.
func (c *FakeHTTPRoutes) Update(ctx context.Context, hTTPRoute *v1alpha1.HTTPRoute, opts v1.UpdateOptions) (result *v1alpha1.HTTPRoute, err error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateAction(httproutesResource, c.ns, hTTPRoute), &v1alpha1.HTTPRoute{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.HTTPRoute), err
}
// Delete takes name of the hTTPRoute and deletes it. Returns an error if one occurs.
func (c *FakeHTTPRoutes) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewDeleteActionWithOptions(httproutesResource, c.ns, name, opts), &v1alpha1.HTTPRoute{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeHTTPRoutes) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
action := testing.NewDeleteCollectionAction(httproutesResource, c.ns, listOpts)
_, err := c.Fake.Invokes(action, &v1alpha1.HTTPRouteList{})
return err
}
// Patch applies the patch and returns the patched hTTPRoute.
func (c *FakeHTTPRoutes) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.HTTPRoute, err error) {
obj, err := c.Fake.
Invokes(testing.NewPatchSubresourceAction(httproutesResource, c.ns, name, pt, data, subresources...), &v1alpha1.HTTPRoute{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.HTTPRoute), err
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
"context"
v1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
)
// FakeMeshTLSAuthentications implements MeshTLSAuthenticationInterface
type FakeMeshTLSAuthentications struct {
Fake *FakePolicyV1alpha1
ns string
}
var meshtlsauthenticationsResource = v1alpha1.SchemeGroupVersion.WithResource("meshtlsauthentications")
var meshtlsauthenticationsKind = v1alpha1.SchemeGroupVersion.WithKind("MeshTLSAuthentication")
// Get takes name of the meshTLSAuthentication, and returns the corresponding meshTLSAuthentication object, and an error if there is any.
func (c *FakeMeshTLSAuthentications) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.MeshTLSAuthentication, err error) {
obj, err := c.Fake.
Invokes(testing.NewGetAction(meshtlsauthenticationsResource, c.ns, name), &v1alpha1.MeshTLSAuthentication{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.MeshTLSAuthentication), err
}
// List takes label and field selectors, and returns the list of MeshTLSAuthentications that match those selectors.
func (c *FakeMeshTLSAuthentications) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.MeshTLSAuthenticationList, err error) {
obj, err := c.Fake.
Invokes(testing.NewListAction(meshtlsauthenticationsResource, meshtlsauthenticationsKind, c.ns, opts), &v1alpha1.MeshTLSAuthenticationList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &v1alpha1.MeshTLSAuthenticationList{ListMeta: obj.(*v1alpha1.MeshTLSAuthenticationList).ListMeta}
for _, item := range obj.(*v1alpha1.MeshTLSAuthenticationList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested meshTLSAuthentications.
func (c *FakeMeshTLSAuthentications) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewWatchAction(meshtlsauthenticationsResource, c.ns, opts))
}
// Create takes the representation of a meshTLSAuthentication and creates it. Returns the server's representation of the meshTLSAuthentication, and an error, if there is any.
func (c *FakeMeshTLSAuthentications) Create(ctx context.Context, meshTLSAuthentication *v1alpha1.MeshTLSAuthentication, opts v1.CreateOptions) (result *v1alpha1.MeshTLSAuthentication, err error) {
obj, err := c.Fake.
Invokes(testing.NewCreateAction(meshtlsauthenticationsResource, c.ns, meshTLSAuthentication), &v1alpha1.MeshTLSAuthentication{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.MeshTLSAuthentication), err
}
// Update takes the representation of a meshTLSAuthentication and updates it. Returns the server's representation of the meshTLSAuthentication, and an error, if there is any.
func (c *FakeMeshTLSAuthentications) Update(ctx context.Context, meshTLSAuthentication *v1alpha1.MeshTLSAuthentication, opts v1.UpdateOptions) (result *v1alpha1.MeshTLSAuthentication, err error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateAction(meshtlsauthenticationsResource, c.ns, meshTLSAuthentication), &v1alpha1.MeshTLSAuthentication{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.MeshTLSAuthentication), err
}
// Delete takes name of the meshTLSAuthentication and deletes it. Returns an error if one occurs.
func (c *FakeMeshTLSAuthentications) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewDeleteActionWithOptions(meshtlsauthenticationsResource, c.ns, name, opts), &v1alpha1.MeshTLSAuthentication{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeMeshTLSAuthentications) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
action := testing.NewDeleteCollectionAction(meshtlsauthenticationsResource, c.ns, listOpts)
_, err := c.Fake.Invokes(action, &v1alpha1.MeshTLSAuthenticationList{})
return err
}
// Patch applies the patch and returns the patched meshTLSAuthentication.
func (c *FakeMeshTLSAuthentications) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.MeshTLSAuthentication, err error) {
obj, err := c.Fake.
Invokes(testing.NewPatchSubresourceAction(meshtlsauthenticationsResource, c.ns, name, pt, data, subresources...), &v1alpha1.MeshTLSAuthentication{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.MeshTLSAuthentication), err
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
"context"
v1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
)
// FakeNetworkAuthentications implements NetworkAuthenticationInterface
type FakeNetworkAuthentications struct {
Fake *FakePolicyV1alpha1
ns string
}
var networkauthenticationsResource = v1alpha1.SchemeGroupVersion.WithResource("networkauthentications")
var networkauthenticationsKind = v1alpha1.SchemeGroupVersion.WithKind("NetworkAuthentication")
// Get takes name of the networkAuthentication, and returns the corresponding networkAuthentication object, and an error if there is any.
func (c *FakeNetworkAuthentications) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.NetworkAuthentication, err error) {
obj, err := c.Fake.
Invokes(testing.NewGetAction(networkauthenticationsResource, c.ns, name), &v1alpha1.NetworkAuthentication{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.NetworkAuthentication), err
}
// List takes label and field selectors, and returns the list of NetworkAuthentications that match those selectors.
func (c *FakeNetworkAuthentications) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.NetworkAuthenticationList, err error) {
obj, err := c.Fake.
Invokes(testing.NewListAction(networkauthenticationsResource, networkauthenticationsKind, c.ns, opts), &v1alpha1.NetworkAuthenticationList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &v1alpha1.NetworkAuthenticationList{ListMeta: obj.(*v1alpha1.NetworkAuthenticationList).ListMeta}
for _, item := range obj.(*v1alpha1.NetworkAuthenticationList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested networkAuthentications.
func (c *FakeNetworkAuthentications) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewWatchAction(networkauthenticationsResource, c.ns, opts))
}
// Create takes the representation of a networkAuthentication and creates it. Returns the server's representation of the networkAuthentication, and an error, if there is any.
func (c *FakeNetworkAuthentications) Create(ctx context.Context, networkAuthentication *v1alpha1.NetworkAuthentication, opts v1.CreateOptions) (result *v1alpha1.NetworkAuthentication, err error) {
obj, err := c.Fake.
Invokes(testing.NewCreateAction(networkauthenticationsResource, c.ns, networkAuthentication), &v1alpha1.NetworkAuthentication{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.NetworkAuthentication), err
}
// Update takes the representation of a networkAuthentication and updates it. Returns the server's representation of the networkAuthentication, and an error, if there is any.
func (c *FakeNetworkAuthentications) Update(ctx context.Context, networkAuthentication *v1alpha1.NetworkAuthentication, opts v1.UpdateOptions) (result *v1alpha1.NetworkAuthentication, err error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateAction(networkauthenticationsResource, c.ns, networkAuthentication), &v1alpha1.NetworkAuthentication{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.NetworkAuthentication), err
}
// Delete takes name of the networkAuthentication and deletes it. Returns an error if one occurs.
func (c *FakeNetworkAuthentications) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewDeleteActionWithOptions(networkauthenticationsResource, c.ns, name, opts), &v1alpha1.NetworkAuthentication{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeNetworkAuthentications) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
action := testing.NewDeleteCollectionAction(networkauthenticationsResource, c.ns, listOpts)
_, err := c.Fake.Invokes(action, &v1alpha1.NetworkAuthenticationList{})
return err
}
// Patch applies the patch and returns the patched networkAuthentication.
func (c *FakeNetworkAuthentications) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.NetworkAuthentication, err error) {
obj, err := c.Fake.
Invokes(testing.NewPatchSubresourceAction(networkauthenticationsResource, c.ns, name, pt, data, subresources...), &v1alpha1.NetworkAuthentication{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.NetworkAuthentication), err
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
v1alpha1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/policy/v1alpha1"
rest "k8s.io/client-go/rest"
testing "k8s.io/client-go/testing"
)
type FakePolicyV1alpha1 struct {
*testing.Fake
}
func (c *FakePolicyV1alpha1) AuthorizationPolicies(namespace string) v1alpha1.AuthorizationPolicyInterface {
return &FakeAuthorizationPolicies{c, namespace}
}
func (c *FakePolicyV1alpha1) HTTPRoutes(namespace string) v1alpha1.HTTPRouteInterface {
return &FakeHTTPRoutes{c, namespace}
}
func (c *FakePolicyV1alpha1) MeshTLSAuthentications(namespace string) v1alpha1.MeshTLSAuthenticationInterface {
return &FakeMeshTLSAuthentications{c, namespace}
}
func (c *FakePolicyV1alpha1) NetworkAuthentications(namespace string) v1alpha1.NetworkAuthenticationInterface {
return &FakeNetworkAuthentications{c, namespace}
}
// RESTClient returns a RESTClient that is used to communicate
// with API server by this client implementation.
func (c *FakePolicyV1alpha1) RESTClient() rest.Interface {
var ret *rest.RESTClient
return ret
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package v1alpha1
import (
"context"
"time"
v1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1"
scheme "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
rest "k8s.io/client-go/rest"
)
// HTTPRoutesGetter has a method to return a HTTPRouteInterface.
// A group's client should implement this interface.
type HTTPRoutesGetter interface {
HTTPRoutes(namespace string) HTTPRouteInterface
}
// HTTPRouteInterface has methods to work with HTTPRoute resources.
type HTTPRouteInterface interface {
Create(ctx context.Context, hTTPRoute *v1alpha1.HTTPRoute, opts v1.CreateOptions) (*v1alpha1.HTTPRoute, error)
Update(ctx context.Context, hTTPRoute *v1alpha1.HTTPRoute, opts v1.UpdateOptions) (*v1alpha1.HTTPRoute, 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) (*v1alpha1.HTTPRoute, error)
List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.HTTPRouteList, error)
Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.HTTPRoute, err error)
HTTPRouteExpansion
}
// hTTPRoutes implements HTTPRouteInterface
type hTTPRoutes struct {
client rest.Interface
ns string
}
// newHTTPRoutes returns a HTTPRoutes
func newHTTPRoutes(c *PolicyV1alpha1Client, namespace string) *hTTPRoutes {
return &hTTPRoutes{
client: c.RESTClient(),
ns: namespace,
}
}
// Get takes name of the hTTPRoute, and returns the corresponding hTTPRoute object, and an error if there is any.
func (c *hTTPRoutes) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.HTTPRoute, err error) {
result = &v1alpha1.HTTPRoute{}
err = c.client.Get().
Namespace(c.ns).
Resource("httproutes").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do(ctx).
Into(result)
return
}
// List takes label and field selectors, and returns the list of HTTPRoutes that match those selectors.
func (c *hTTPRoutes) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.HTTPRouteList, err error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
result = &v1alpha1.HTTPRouteList{}
err = c.client.Get().
Namespace(c.ns).
Resource("httproutes").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Do(ctx).
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested hTTPRoutes.
func (c *hTTPRoutes) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
opts.Watch = true
return c.client.Get().
Namespace(c.ns).
Resource("httproutes").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Watch(ctx)
}
// Create takes the representation of a hTTPRoute and creates it. Returns the server's representation of the hTTPRoute, and an error, if there is any.
func (c *hTTPRoutes) Create(ctx context.Context, hTTPRoute *v1alpha1.HTTPRoute, opts v1.CreateOptions) (result *v1alpha1.HTTPRoute, err error) {
result = &v1alpha1.HTTPRoute{}
err = c.client.Post().
Namespace(c.ns).
Resource("httproutes").
VersionedParams(&opts, scheme.ParameterCodec).
Body(hTTPRoute).
Do(ctx).
Into(result)
return
}
// Update takes the representation of a hTTPRoute and updates it. Returns the server's representation of the hTTPRoute, and an error, if there is any.
func (c *hTTPRoutes) Update(ctx context.Context, hTTPRoute *v1alpha1.HTTPRoute, opts v1.UpdateOptions) (result *v1alpha1.HTTPRoute, err error) {
result = &v1alpha1.HTTPRoute{}
err = c.client.Put().
Namespace(c.ns).
Resource("httproutes").
Name(hTTPRoute.Name).
VersionedParams(&opts, scheme.ParameterCodec).
Body(hTTPRoute).
Do(ctx).
Into(result)
return
}
// Delete takes name of the hTTPRoute and deletes it. Returns an error if one occurs.
func (c *hTTPRoutes) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
return c.client.Delete().
Namespace(c.ns).
Resource("httproutes").
Name(name).
Body(&opts).
Do(ctx).
Error()
}
// DeleteCollection deletes a collection of objects.
func (c *hTTPRoutes) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
var timeout time.Duration
if listOpts.TimeoutSeconds != nil {
timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second
}
return c.client.Delete().
Namespace(c.ns).
Resource("httproutes").
VersionedParams(&listOpts, scheme.ParameterCodec).
Timeout(timeout).
Body(&opts).
Do(ctx).
Error()
}
// Patch applies the patch and returns the patched hTTPRoute.
func (c *hTTPRoutes) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.HTTPRoute, err error) {
result = &v1alpha1.HTTPRoute{}
err = c.client.Patch(pt).
Namespace(c.ns).
Resource("httproutes").
Name(name).
SubResource(subresources...).
VersionedParams(&opts, scheme.ParameterCodec).
Body(data).
Do(ctx).
Into(result)
return
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package v1alpha1
import (
"context"
"time"
v1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1"
scheme "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
rest "k8s.io/client-go/rest"
)
// MeshTLSAuthenticationsGetter has a method to return a MeshTLSAuthenticationInterface.
// A group's client should implement this interface.
type MeshTLSAuthenticationsGetter interface {
MeshTLSAuthentications(namespace string) MeshTLSAuthenticationInterface
}
// MeshTLSAuthenticationInterface has methods to work with MeshTLSAuthentication resources.
type MeshTLSAuthenticationInterface interface {
Create(ctx context.Context, meshTLSAuthentication *v1alpha1.MeshTLSAuthentication, opts v1.CreateOptions) (*v1alpha1.MeshTLSAuthentication, error)
Update(ctx context.Context, meshTLSAuthentication *v1alpha1.MeshTLSAuthentication, opts v1.UpdateOptions) (*v1alpha1.MeshTLSAuthentication, 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) (*v1alpha1.MeshTLSAuthentication, error)
List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.MeshTLSAuthenticationList, error)
Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.MeshTLSAuthentication, err error)
MeshTLSAuthenticationExpansion
}
// meshTLSAuthentications implements MeshTLSAuthenticationInterface
type meshTLSAuthentications struct {
client rest.Interface
ns string
}
// newMeshTLSAuthentications returns a MeshTLSAuthentications
func newMeshTLSAuthentications(c *PolicyV1alpha1Client, namespace string) *meshTLSAuthentications {
return &meshTLSAuthentications{
client: c.RESTClient(),
ns: namespace,
}
}
// Get takes name of the meshTLSAuthentication, and returns the corresponding meshTLSAuthentication object, and an error if there is any.
func (c *meshTLSAuthentications) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.MeshTLSAuthentication, err error) {
result = &v1alpha1.MeshTLSAuthentication{}
err = c.client.Get().
Namespace(c.ns).
Resource("meshtlsauthentications").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do(ctx).
Into(result)
return
}
// List takes label and field selectors, and returns the list of MeshTLSAuthentications that match those selectors.
func (c *meshTLSAuthentications) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.MeshTLSAuthenticationList, err error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
result = &v1alpha1.MeshTLSAuthenticationList{}
err = c.client.Get().
Namespace(c.ns).
Resource("meshtlsauthentications").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Do(ctx).
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested meshTLSAuthentications.
func (c *meshTLSAuthentications) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
opts.Watch = true
return c.client.Get().
Namespace(c.ns).
Resource("meshtlsauthentications").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Watch(ctx)
}
// Create takes the representation of a meshTLSAuthentication and creates it. Returns the server's representation of the meshTLSAuthentication, and an error, if there is any.
func (c *meshTLSAuthentications) Create(ctx context.Context, meshTLSAuthentication *v1alpha1.MeshTLSAuthentication, opts v1.CreateOptions) (result *v1alpha1.MeshTLSAuthentication, err error) {
result = &v1alpha1.MeshTLSAuthentication{}
err = c.client.Post().
Namespace(c.ns).
Resource("meshtlsauthentications").
VersionedParams(&opts, scheme.ParameterCodec).
Body(meshTLSAuthentication).
Do(ctx).
Into(result)
return
}
// Update takes the representation of a meshTLSAuthentication and updates it. Returns the server's representation of the meshTLSAuthentication, and an error, if there is any.
func (c *meshTLSAuthentications) Update(ctx context.Context, meshTLSAuthentication *v1alpha1.MeshTLSAuthentication, opts v1.UpdateOptions) (result *v1alpha1.MeshTLSAuthentication, err error) {
result = &v1alpha1.MeshTLSAuthentication{}
err = c.client.Put().
Namespace(c.ns).
Resource("meshtlsauthentications").
Name(meshTLSAuthentication.Name).
VersionedParams(&opts, scheme.ParameterCodec).
Body(meshTLSAuthentication).
Do(ctx).
Into(result)
return
}
// Delete takes name of the meshTLSAuthentication and deletes it. Returns an error if one occurs.
func (c *meshTLSAuthentications) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
return c.client.Delete().
Namespace(c.ns).
Resource("meshtlsauthentications").
Name(name).
Body(&opts).
Do(ctx).
Error()
}
// DeleteCollection deletes a collection of objects.
func (c *meshTLSAuthentications) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
var timeout time.Duration
if listOpts.TimeoutSeconds != nil {
timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second
}
return c.client.Delete().
Namespace(c.ns).
Resource("meshtlsauthentications").
VersionedParams(&listOpts, scheme.ParameterCodec).
Timeout(timeout).
Body(&opts).
Do(ctx).
Error()
}
// Patch applies the patch and returns the patched meshTLSAuthentication.
func (c *meshTLSAuthentications) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.MeshTLSAuthentication, err error) {
result = &v1alpha1.MeshTLSAuthentication{}
err = c.client.Patch(pt).
Namespace(c.ns).
Resource("meshtlsauthentications").
Name(name).
SubResource(subresources...).
VersionedParams(&opts, scheme.ParameterCodec).
Body(data).
Do(ctx).
Into(result)
return
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package v1alpha1
import (
"context"
"time"
v1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1"
scheme "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
rest "k8s.io/client-go/rest"
)
// NetworkAuthenticationsGetter has a method to return a NetworkAuthenticationInterface.
// A group's client should implement this interface.
type NetworkAuthenticationsGetter interface {
NetworkAuthentications(namespace string) NetworkAuthenticationInterface
}
// NetworkAuthenticationInterface has methods to work with NetworkAuthentication resources.
type NetworkAuthenticationInterface interface {
Create(ctx context.Context, networkAuthentication *v1alpha1.NetworkAuthentication, opts v1.CreateOptions) (*v1alpha1.NetworkAuthentication, error)
Update(ctx context.Context, networkAuthentication *v1alpha1.NetworkAuthentication, opts v1.UpdateOptions) (*v1alpha1.NetworkAuthentication, 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) (*v1alpha1.NetworkAuthentication, error)
List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.NetworkAuthenticationList, error)
Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.NetworkAuthentication, err error)
NetworkAuthenticationExpansion
}
// networkAuthentications implements NetworkAuthenticationInterface
type networkAuthentications struct {
client rest.Interface
ns string
}
// newNetworkAuthentications returns a NetworkAuthentications
func newNetworkAuthentications(c *PolicyV1alpha1Client, namespace string) *networkAuthentications {
return &networkAuthentications{
client: c.RESTClient(),
ns: namespace,
}
}
// Get takes name of the networkAuthentication, and returns the corresponding networkAuthentication object, and an error if there is any.
func (c *networkAuthentications) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.NetworkAuthentication, err error) {
result = &v1alpha1.NetworkAuthentication{}
err = c.client.Get().
Namespace(c.ns).
Resource("networkauthentications").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do(ctx).
Into(result)
return
}
// List takes label and field selectors, and returns the list of NetworkAuthentications that match those selectors.
func (c *networkAuthentications) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.NetworkAuthenticationList, err error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
result = &v1alpha1.NetworkAuthenticationList{}
err = c.client.Get().
Namespace(c.ns).
Resource("networkauthentications").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Do(ctx).
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested networkAuthentications.
func (c *networkAuthentications) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
opts.Watch = true
return c.client.Get().
Namespace(c.ns).
Resource("networkauthentications").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Watch(ctx)
}
// Create takes the representation of a networkAuthentication and creates it. Returns the server's representation of the networkAuthentication, and an error, if there is any.
func (c *networkAuthentications) Create(ctx context.Context, networkAuthentication *v1alpha1.NetworkAuthentication, opts v1.CreateOptions) (result *v1alpha1.NetworkAuthentication, err error) {
result = &v1alpha1.NetworkAuthentication{}
err = c.client.Post().
Namespace(c.ns).
Resource("networkauthentications").
VersionedParams(&opts, scheme.ParameterCodec).
Body(networkAuthentication).
Do(ctx).
Into(result)
return
}
// Update takes the representation of a networkAuthentication and updates it. Returns the server's representation of the networkAuthentication, and an error, if there is any.
func (c *networkAuthentications) Update(ctx context.Context, networkAuthentication *v1alpha1.NetworkAuthentication, opts v1.UpdateOptions) (result *v1alpha1.NetworkAuthentication, err error) {
result = &v1alpha1.NetworkAuthentication{}
err = c.client.Put().
Namespace(c.ns).
Resource("networkauthentications").
Name(networkAuthentication.Name).
VersionedParams(&opts, scheme.ParameterCodec).
Body(networkAuthentication).
Do(ctx).
Into(result)
return
}
// Delete takes name of the networkAuthentication and deletes it. Returns an error if one occurs.
func (c *networkAuthentications) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
return c.client.Delete().
Namespace(c.ns).
Resource("networkauthentications").
Name(name).
Body(&opts).
Do(ctx).
Error()
}
// DeleteCollection deletes a collection of objects.
func (c *networkAuthentications) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
var timeout time.Duration
if listOpts.TimeoutSeconds != nil {
timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second
}
return c.client.Delete().
Namespace(c.ns).
Resource("networkauthentications").
VersionedParams(&listOpts, scheme.ParameterCodec).
Timeout(timeout).
Body(&opts).
Do(ctx).
Error()
}
// Patch applies the patch and returns the patched networkAuthentication.
func (c *networkAuthentications) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.NetworkAuthentication, err error) {
result = &v1alpha1.NetworkAuthentication{}
err = c.client.Patch(pt).
Namespace(c.ns).
Resource("networkauthentications").
Name(name).
SubResource(subresources...).
VersionedParams(&opts, scheme.ParameterCodec).
Body(data).
Do(ctx).
Into(result)
return
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package v1alpha1
import (
"net/http"
v1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1"
"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme"
rest "k8s.io/client-go/rest"
)
type PolicyV1alpha1Interface interface {
RESTClient() rest.Interface
AuthorizationPoliciesGetter
HTTPRoutesGetter
MeshTLSAuthenticationsGetter
NetworkAuthenticationsGetter
}
// PolicyV1alpha1Client is used to interact with features provided by the policy group.
type PolicyV1alpha1Client struct {
restClient rest.Interface
}
func (c *PolicyV1alpha1Client) AuthorizationPolicies(namespace string) AuthorizationPolicyInterface {
return newAuthorizationPolicies(c, namespace)
}
func (c *PolicyV1alpha1Client) HTTPRoutes(namespace string) HTTPRouteInterface {
return newHTTPRoutes(c, namespace)
}
func (c *PolicyV1alpha1Client) MeshTLSAuthentications(namespace string) MeshTLSAuthenticationInterface {
return newMeshTLSAuthentications(c, namespace)
}
func (c *PolicyV1alpha1Client) NetworkAuthentications(namespace string) NetworkAuthenticationInterface {
return newNetworkAuthentications(c, namespace)
}
// NewForConfig creates a new PolicyV1alpha1Client for the given config.
// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
// where httpClient was generated with rest.HTTPClientFor(c).
func NewForConfig(c *rest.Config) (*PolicyV1alpha1Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
httpClient, err := rest.HTTPClientFor(&config)
if err != nil {
return nil, err
}
return NewForConfigAndClient(&config, httpClient)
}
// NewForConfigAndClient creates a new PolicyV1alpha1Client for the given config and http client.
// Note the http client provided takes precedence over the configured transport values.
func NewForConfigAndClient(c *rest.Config, h *http.Client) (*PolicyV1alpha1Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
client, err := rest.RESTClientForConfigAndClient(&config, h)
if err != nil {
return nil, err
}
return &PolicyV1alpha1Client{client}, nil
}
// NewForConfigOrDie creates a new PolicyV1alpha1Client for the given config and
// panics if there is an error in the config.
func NewForConfigOrDie(c *rest.Config) *PolicyV1alpha1Client {
client, err := NewForConfig(c)
if err != nil {
panic(err)
}
return client
}
// New creates a new PolicyV1alpha1Client for the given RESTClient.
func New(c rest.Interface) *PolicyV1alpha1Client {
return &PolicyV1alpha1Client{c}
}
func setConfigDefaults(config *rest.Config) error {
gv := v1alpha1.SchemeGroupVersion
config.GroupVersion = &gv
config.APIPath = "/apis"
config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
if config.UserAgent == "" {
config.UserAgent = rest.DefaultKubernetesUserAgent()
}
return nil
}
// RESTClient returns a RESTClient that is used to communicate
// with API server by this client implementation.
func (c *PolicyV1alpha1Client) RESTClient() rest.Interface {
if c == nil {
return nil
}
return c.restClient
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
"context"
v1beta3 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1beta3"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
)
// FakeHTTPRoutes implements HTTPRouteInterface
type FakeHTTPRoutes struct {
Fake *FakePolicyV1beta3
ns string
}
var httproutesResource = v1beta3.SchemeGroupVersion.WithResource("httproutes")
var httproutesKind = v1beta3.SchemeGroupVersion.WithKind("HTTPRoute")
// Get takes name of the hTTPRoute, and returns the corresponding hTTPRoute object, and an error if there is any.
func (c *FakeHTTPRoutes) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta3.HTTPRoute, err error) {
obj, err := c.Fake.
Invokes(testing.NewGetAction(httproutesResource, c.ns, name), &v1beta3.HTTPRoute{})
if obj == nil {
return nil, err
}
return obj.(*v1beta3.HTTPRoute), err
}
// List takes label and field selectors, and returns the list of HTTPRoutes that match those selectors.
func (c *FakeHTTPRoutes) List(ctx context.Context, opts v1.ListOptions) (result *v1beta3.HTTPRouteList, err error) {
obj, err := c.Fake.
Invokes(testing.NewListAction(httproutesResource, httproutesKind, c.ns, opts), &v1beta3.HTTPRouteList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &v1beta3.HTTPRouteList{ListMeta: obj.(*v1beta3.HTTPRouteList).ListMeta}
for _, item := range obj.(*v1beta3.HTTPRouteList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested hTTPRoutes.
func (c *FakeHTTPRoutes) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewWatchAction(httproutesResource, c.ns, opts))
}
// Create takes the representation of a hTTPRoute and creates it. Returns the server's representation of the hTTPRoute, and an error, if there is any.
func (c *FakeHTTPRoutes) Create(ctx context.Context, hTTPRoute *v1beta3.HTTPRoute, opts v1.CreateOptions) (result *v1beta3.HTTPRoute, err error) {
obj, err := c.Fake.
Invokes(testing.NewCreateAction(httproutesResource, c.ns, hTTPRoute), &v1beta3.HTTPRoute{})
if obj == nil {
return nil, err
}
return obj.(*v1beta3.HTTPRoute), err
}
// Update takes the representation of a hTTPRoute and updates it. Returns the server's representation of the hTTPRoute, and an error, if there is any.
func (c *FakeHTTPRoutes) Update(ctx context.Context, hTTPRoute *v1beta3.HTTPRoute, opts v1.UpdateOptions) (result *v1beta3.HTTPRoute, err error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateAction(httproutesResource, c.ns, hTTPRoute), &v1beta3.HTTPRoute{})
if obj == nil {
return nil, err
}
return obj.(*v1beta3.HTTPRoute), err
}
// Delete takes name of the hTTPRoute and deletes it. Returns an error if one occurs.
func (c *FakeHTTPRoutes) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewDeleteActionWithOptions(httproutesResource, c.ns, name, opts), &v1beta3.HTTPRoute{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeHTTPRoutes) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
action := testing.NewDeleteCollectionAction(httproutesResource, c.ns, listOpts)
_, err := c.Fake.Invokes(action, &v1beta3.HTTPRouteList{})
return err
}
// Patch applies the patch and returns the patched hTTPRoute.
func (c *FakeHTTPRoutes) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta3.HTTPRoute, err error) {
obj, err := c.Fake.
Invokes(testing.NewPatchSubresourceAction(httproutesResource, c.ns, name, pt, data, subresources...), &v1beta3.HTTPRoute{})
if obj == nil {
return nil, err
}
return obj.(*v1beta3.HTTPRoute), err
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
v1beta3 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/policy/v1beta3"
rest "k8s.io/client-go/rest"
testing "k8s.io/client-go/testing"
)
type FakePolicyV1beta3 struct {
*testing.Fake
}
func (c *FakePolicyV1beta3) HTTPRoutes(namespace string) v1beta3.HTTPRouteInterface {
return &FakeHTTPRoutes{c, namespace}
}
// RESTClient returns a RESTClient that is used to communicate
// with API server by this client implementation.
func (c *FakePolicyV1beta3) RESTClient() rest.Interface {
var ret *rest.RESTClient
return ret
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package v1beta3
import (
"context"
"time"
v1beta3 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1beta3"
scheme "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
rest "k8s.io/client-go/rest"
)
// HTTPRoutesGetter has a method to return a HTTPRouteInterface.
// A group's client should implement this interface.
type HTTPRoutesGetter interface {
HTTPRoutes(namespace string) HTTPRouteInterface
}
// HTTPRouteInterface has methods to work with HTTPRoute resources.
type HTTPRouteInterface interface {
Create(ctx context.Context, hTTPRoute *v1beta3.HTTPRoute, opts v1.CreateOptions) (*v1beta3.HTTPRoute, error)
Update(ctx context.Context, hTTPRoute *v1beta3.HTTPRoute, opts v1.UpdateOptions) (*v1beta3.HTTPRoute, 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) (*v1beta3.HTTPRoute, error)
List(ctx context.Context, opts v1.ListOptions) (*v1beta3.HTTPRouteList, error)
Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta3.HTTPRoute, err error)
HTTPRouteExpansion
}
// hTTPRoutes implements HTTPRouteInterface
type hTTPRoutes struct {
client rest.Interface
ns string
}
// newHTTPRoutes returns a HTTPRoutes
func newHTTPRoutes(c *PolicyV1beta3Client, namespace string) *hTTPRoutes {
return &hTTPRoutes{
client: c.RESTClient(),
ns: namespace,
}
}
// Get takes name of the hTTPRoute, and returns the corresponding hTTPRoute object, and an error if there is any.
func (c *hTTPRoutes) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta3.HTTPRoute, err error) {
result = &v1beta3.HTTPRoute{}
err = c.client.Get().
Namespace(c.ns).
Resource("httproutes").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do(ctx).
Into(result)
return
}
// List takes label and field selectors, and returns the list of HTTPRoutes that match those selectors.
func (c *hTTPRoutes) List(ctx context.Context, opts v1.ListOptions) (result *v1beta3.HTTPRouteList, err error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
result = &v1beta3.HTTPRouteList{}
err = c.client.Get().
Namespace(c.ns).
Resource("httproutes").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Do(ctx).
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested hTTPRoutes.
func (c *hTTPRoutes) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
opts.Watch = true
return c.client.Get().
Namespace(c.ns).
Resource("httproutes").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Watch(ctx)
}
// Create takes the representation of a hTTPRoute and creates it. Returns the server's representation of the hTTPRoute, and an error, if there is any.
func (c *hTTPRoutes) Create(ctx context.Context, hTTPRoute *v1beta3.HTTPRoute, opts v1.CreateOptions) (result *v1beta3.HTTPRoute, err error) {
result = &v1beta3.HTTPRoute{}
err = c.client.Post().
Namespace(c.ns).
Resource("httproutes").
VersionedParams(&opts, scheme.ParameterCodec).
Body(hTTPRoute).
Do(ctx).
Into(result)
return
}
// Update takes the representation of a hTTPRoute and updates it. Returns the server's representation of the hTTPRoute, and an error, if there is any.
func (c *hTTPRoutes) Update(ctx context.Context, hTTPRoute *v1beta3.HTTPRoute, opts v1.UpdateOptions) (result *v1beta3.HTTPRoute, err error) {
result = &v1beta3.HTTPRoute{}
err = c.client.Put().
Namespace(c.ns).
Resource("httproutes").
Name(hTTPRoute.Name).
VersionedParams(&opts, scheme.ParameterCodec).
Body(hTTPRoute).
Do(ctx).
Into(result)
return
}
// Delete takes name of the hTTPRoute and deletes it. Returns an error if one occurs.
func (c *hTTPRoutes) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
return c.client.Delete().
Namespace(c.ns).
Resource("httproutes").
Name(name).
Body(&opts).
Do(ctx).
Error()
}
// DeleteCollection deletes a collection of objects.
func (c *hTTPRoutes) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
var timeout time.Duration
if listOpts.TimeoutSeconds != nil {
timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second
}
return c.client.Delete().
Namespace(c.ns).
Resource("httproutes").
VersionedParams(&listOpts, scheme.ParameterCodec).
Timeout(timeout).
Body(&opts).
Do(ctx).
Error()
}
// Patch applies the patch and returns the patched hTTPRoute.
func (c *hTTPRoutes) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta3.HTTPRoute, err error) {
result = &v1beta3.HTTPRoute{}
err = c.client.Patch(pt).
Namespace(c.ns).
Resource("httproutes").
Name(name).
SubResource(subresources...).
VersionedParams(&opts, scheme.ParameterCodec).
Body(data).
Do(ctx).
Into(result)
return
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package v1beta3
import (
"net/http"
v1beta3 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1beta3"
"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme"
rest "k8s.io/client-go/rest"
)
type PolicyV1beta3Interface interface {
RESTClient() rest.Interface
HTTPRoutesGetter
}
// PolicyV1beta3Client is used to interact with features provided by the policy group.
type PolicyV1beta3Client struct {
restClient rest.Interface
}
func (c *PolicyV1beta3Client) HTTPRoutes(namespace string) HTTPRouteInterface {
return newHTTPRoutes(c, namespace)
}
// NewForConfig creates a new PolicyV1beta3Client for the given config.
// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
// where httpClient was generated with rest.HTTPClientFor(c).
func NewForConfig(c *rest.Config) (*PolicyV1beta3Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
httpClient, err := rest.HTTPClientFor(&config)
if err != nil {
return nil, err
}
return NewForConfigAndClient(&config, httpClient)
}
// NewForConfigAndClient creates a new PolicyV1beta3Client for the given config and http client.
// Note the http client provided takes precedence over the configured transport values.
func NewForConfigAndClient(c *rest.Config, h *http.Client) (*PolicyV1beta3Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
client, err := rest.RESTClientForConfigAndClient(&config, h)
if err != nil {
return nil, err
}
return &PolicyV1beta3Client{client}, nil
}
// NewForConfigOrDie creates a new PolicyV1beta3Client for the given config and
// panics if there is an error in the config.
func NewForConfigOrDie(c *rest.Config) *PolicyV1beta3Client {
client, err := NewForConfig(c)
if err != nil {
panic(err)
}
return client
}
// New creates a new PolicyV1beta3Client for the given RESTClient.
func New(c rest.Interface) *PolicyV1beta3Client {
return &PolicyV1beta3Client{c}
}
func setConfigDefaults(config *rest.Config) error {
gv := v1beta3.SchemeGroupVersion
config.GroupVersion = &gv
config.APIPath = "/apis"
config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
if config.UserAgent == "" {
config.UserAgent = rest.DefaultKubernetesUserAgent()
}
return nil
}
// RESTClient returns a RESTClient that is used to communicate
// with API server by this client implementation.
func (c *PolicyV1beta3Client) RESTClient() rest.Interface {
if c == nil {
return nil
}
return c.restClient
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
"context"
v1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
)
// FakeServers implements ServerInterface
type FakeServers struct {
Fake *FakeServerV1beta1
ns string
}
var serversResource = v1beta1.SchemeGroupVersion.WithResource("servers")
var serversKind = v1beta1.SchemeGroupVersion.WithKind("Server")
// Get takes name of the server, and returns the corresponding server object, and an error if there is any.
func (c *FakeServers) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.Server, err error) {
obj, err := c.Fake.
Invokes(testing.NewGetAction(serversResource, c.ns, name), &v1beta1.Server{})
if obj == nil {
return nil, err
}
return obj.(*v1beta1.Server), err
}
// List takes label and field selectors, and returns the list of Servers that match those selectors.
func (c *FakeServers) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.ServerList, err error) {
obj, err := c.Fake.
Invokes(testing.NewListAction(serversResource, serversKind, c.ns, opts), &v1beta1.ServerList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &v1beta1.ServerList{ListMeta: obj.(*v1beta1.ServerList).ListMeta}
for _, item := range obj.(*v1beta1.ServerList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested servers.
func (c *FakeServers) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewWatchAction(serversResource, c.ns, opts))
}
// Create takes the representation of a server and creates it. Returns the server's representation of the server, and an error, if there is any.
func (c *FakeServers) Create(ctx context.Context, server *v1beta1.Server, opts v1.CreateOptions) (result *v1beta1.Server, err error) {
obj, err := c.Fake.
Invokes(testing.NewCreateAction(serversResource, c.ns, server), &v1beta1.Server{})
if obj == nil {
return nil, err
}
return obj.(*v1beta1.Server), err
}
// Update takes the representation of a server and updates it. Returns the server's representation of the server, and an error, if there is any.
func (c *FakeServers) Update(ctx context.Context, server *v1beta1.Server, opts v1.UpdateOptions) (result *v1beta1.Server, err error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateAction(serversResource, c.ns, server), &v1beta1.Server{})
if obj == nil {
return nil, err
}
return obj.(*v1beta1.Server), err
}
// Delete takes name of the server and deletes it. Returns an error if one occurs.
func (c *FakeServers) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewDeleteActionWithOptions(serversResource, c.ns, name, opts), &v1beta1.Server{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeServers) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
action := testing.NewDeleteCollectionAction(serversResource, c.ns, listOpts)
_, err := c.Fake.Invokes(action, &v1beta1.ServerList{})
return err
}
// Patch applies the patch and returns the patched server.
func (c *FakeServers) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.Server, err error) {
obj, err := c.Fake.
Invokes(testing.NewPatchSubresourceAction(serversResource, c.ns, name, pt, data, subresources...), &v1beta1.Server{})
if obj == nil {
return nil, err
}
return obj.(*v1beta1.Server), err
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
v1beta1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/server/v1beta1"
rest "k8s.io/client-go/rest"
testing "k8s.io/client-go/testing"
)
type FakeServerV1beta1 struct {
*testing.Fake
}
func (c *FakeServerV1beta1) Servers(namespace string) v1beta1.ServerInterface {
return &FakeServers{c, namespace}
}
// RESTClient returns a RESTClient that is used to communicate
// with API server by this client implementation.
func (c *FakeServerV1beta1) RESTClient() rest.Interface {
var ret *rest.RESTClient
return ret
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package v1beta1
import (
"context"
"time"
v1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta1"
scheme "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
rest "k8s.io/client-go/rest"
)
// ServersGetter has a method to return a ServerInterface.
// A group's client should implement this interface.
type ServersGetter interface {
Servers(namespace string) ServerInterface
}
// ServerInterface has methods to work with Server resources.
type ServerInterface interface {
Create(ctx context.Context, server *v1beta1.Server, opts v1.CreateOptions) (*v1beta1.Server, error)
Update(ctx context.Context, server *v1beta1.Server, opts v1.UpdateOptions) (*v1beta1.Server, 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) (*v1beta1.Server, error)
List(ctx context.Context, opts v1.ListOptions) (*v1beta1.ServerList, error)
Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.Server, err error)
ServerExpansion
}
// servers implements ServerInterface
type servers struct {
client rest.Interface
ns string
}
// newServers returns a Servers
func newServers(c *ServerV1beta1Client, namespace string) *servers {
return &servers{
client: c.RESTClient(),
ns: namespace,
}
}
// Get takes name of the server, and returns the corresponding server object, and an error if there is any.
func (c *servers) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.Server, err error) {
result = &v1beta1.Server{}
err = c.client.Get().
Namespace(c.ns).
Resource("servers").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do(ctx).
Into(result)
return
}
// List takes label and field selectors, and returns the list of Servers that match those selectors.
func (c *servers) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.ServerList, err error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
result = &v1beta1.ServerList{}
err = c.client.Get().
Namespace(c.ns).
Resource("servers").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Do(ctx).
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested servers.
func (c *servers) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
opts.Watch = true
return c.client.Get().
Namespace(c.ns).
Resource("servers").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Watch(ctx)
}
// Create takes the representation of a server and creates it. Returns the server's representation of the server, and an error, if there is any.
func (c *servers) Create(ctx context.Context, server *v1beta1.Server, opts v1.CreateOptions) (result *v1beta1.Server, err error) {
result = &v1beta1.Server{}
err = c.client.Post().
Namespace(c.ns).
Resource("servers").
VersionedParams(&opts, scheme.ParameterCodec).
Body(server).
Do(ctx).
Into(result)
return
}
// Update takes the representation of a server and updates it. Returns the server's representation of the server, and an error, if there is any.
func (c *servers) Update(ctx context.Context, server *v1beta1.Server, opts v1.UpdateOptions) (result *v1beta1.Server, err error) {
result = &v1beta1.Server{}
err = c.client.Put().
Namespace(c.ns).
Resource("servers").
Name(server.Name).
VersionedParams(&opts, scheme.ParameterCodec).
Body(server).
Do(ctx).
Into(result)
return
}
// Delete takes name of the server and deletes it. Returns an error if one occurs.
func (c *servers) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
return c.client.Delete().
Namespace(c.ns).
Resource("servers").
Name(name).
Body(&opts).
Do(ctx).
Error()
}
// DeleteCollection deletes a collection of objects.
func (c *servers) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
var timeout time.Duration
if listOpts.TimeoutSeconds != nil {
timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second
}
return c.client.Delete().
Namespace(c.ns).
Resource("servers").
VersionedParams(&listOpts, scheme.ParameterCodec).
Timeout(timeout).
Body(&opts).
Do(ctx).
Error()
}
// Patch applies the patch and returns the patched server.
func (c *servers) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.Server, err error) {
result = &v1beta1.Server{}
err = c.client.Patch(pt).
Namespace(c.ns).
Resource("servers").
Name(name).
SubResource(subresources...).
VersionedParams(&opts, scheme.ParameterCodec).
Body(data).
Do(ctx).
Into(result)
return
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package v1beta1
import (
"net/http"
v1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta1"
"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme"
rest "k8s.io/client-go/rest"
)
type ServerV1beta1Interface interface {
RESTClient() rest.Interface
ServersGetter
}
// ServerV1beta1Client is used to interact with features provided by the server group.
type ServerV1beta1Client struct {
restClient rest.Interface
}
func (c *ServerV1beta1Client) Servers(namespace string) ServerInterface {
return newServers(c, namespace)
}
// NewForConfig creates a new ServerV1beta1Client for the given config.
// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
// where httpClient was generated with rest.HTTPClientFor(c).
func NewForConfig(c *rest.Config) (*ServerV1beta1Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
httpClient, err := rest.HTTPClientFor(&config)
if err != nil {
return nil, err
}
return NewForConfigAndClient(&config, httpClient)
}
// NewForConfigAndClient creates a new ServerV1beta1Client for the given config and http client.
// Note the http client provided takes precedence over the configured transport values.
func NewForConfigAndClient(c *rest.Config, h *http.Client) (*ServerV1beta1Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
client, err := rest.RESTClientForConfigAndClient(&config, h)
if err != nil {
return nil, err
}
return &ServerV1beta1Client{client}, nil
}
// NewForConfigOrDie creates a new ServerV1beta1Client for the given config and
// panics if there is an error in the config.
func NewForConfigOrDie(c *rest.Config) *ServerV1beta1Client {
client, err := NewForConfig(c)
if err != nil {
panic(err)
}
return client
}
// New creates a new ServerV1beta1Client for the given RESTClient.
func New(c rest.Interface) *ServerV1beta1Client {
return &ServerV1beta1Client{c}
}
func setConfigDefaults(config *rest.Config) error {
gv := v1beta1.SchemeGroupVersion
config.GroupVersion = &gv
config.APIPath = "/apis"
config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
if config.UserAgent == "" {
config.UserAgent = rest.DefaultKubernetesUserAgent()
}
return nil
}
// RESTClient returns a RESTClient that is used to communicate
// with API server by this client implementation.
func (c *ServerV1beta1Client) RESTClient() rest.Interface {
if c == nil {
return nil
}
return c.restClient
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
"context"
v1beta2 "github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta2"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
)
// FakeServers implements ServerInterface
type FakeServers struct {
Fake *FakeServerV1beta2
ns string
}
var serversResource = v1beta2.SchemeGroupVersion.WithResource("servers")
var serversKind = v1beta2.SchemeGroupVersion.WithKind("Server")
// Get takes name of the server, and returns the corresponding server object, and an error if there is any.
func (c *FakeServers) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta2.Server, err error) {
obj, err := c.Fake.
Invokes(testing.NewGetAction(serversResource, c.ns, name), &v1beta2.Server{})
if obj == nil {
return nil, err
}
return obj.(*v1beta2.Server), err
}
// List takes label and field selectors, and returns the list of Servers that match those selectors.
func (c *FakeServers) List(ctx context.Context, opts v1.ListOptions) (result *v1beta2.ServerList, err error) {
obj, err := c.Fake.
Invokes(testing.NewListAction(serversResource, serversKind, c.ns, opts), &v1beta2.ServerList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &v1beta2.ServerList{ListMeta: obj.(*v1beta2.ServerList).ListMeta}
for _, item := range obj.(*v1beta2.ServerList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested servers.
func (c *FakeServers) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewWatchAction(serversResource, c.ns, opts))
}
// Create takes the representation of a server and creates it. Returns the server's representation of the server, and an error, if there is any.
func (c *FakeServers) Create(ctx context.Context, server *v1beta2.Server, opts v1.CreateOptions) (result *v1beta2.Server, err error) {
obj, err := c.Fake.
Invokes(testing.NewCreateAction(serversResource, c.ns, server), &v1beta2.Server{})
if obj == nil {
return nil, err
}
return obj.(*v1beta2.Server), err
}
// Update takes the representation of a server and updates it. Returns the server's representation of the server, and an error, if there is any.
func (c *FakeServers) Update(ctx context.Context, server *v1beta2.Server, opts v1.UpdateOptions) (result *v1beta2.Server, err error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateAction(serversResource, c.ns, server), &v1beta2.Server{})
if obj == nil {
return nil, err
}
return obj.(*v1beta2.Server), err
}
// Delete takes name of the server and deletes it. Returns an error if one occurs.
func (c *FakeServers) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewDeleteActionWithOptions(serversResource, c.ns, name, opts), &v1beta2.Server{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeServers) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
action := testing.NewDeleteCollectionAction(serversResource, c.ns, listOpts)
_, err := c.Fake.Invokes(action, &v1beta2.ServerList{})
return err
}
// Patch applies the patch and returns the patched server.
func (c *FakeServers) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta2.Server, err error) {
obj, err := c.Fake.
Invokes(testing.NewPatchSubresourceAction(serversResource, c.ns, name, pt, data, subresources...), &v1beta2.Server{})
if obj == nil {
return nil, err
}
return obj.(*v1beta2.Server), err
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
v1beta2 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/server/v1beta2"
rest "k8s.io/client-go/rest"
testing "k8s.io/client-go/testing"
)
type FakeServerV1beta2 struct {
*testing.Fake
}
func (c *FakeServerV1beta2) Servers(namespace string) v1beta2.ServerInterface {
return &FakeServers{c, namespace}
}
// RESTClient returns a RESTClient that is used to communicate
// with API server by this client implementation.
func (c *FakeServerV1beta2) RESTClient() rest.Interface {
var ret *rest.RESTClient
return ret
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package v1beta2
import (
"context"
"time"
v1beta2 "github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta2"
scheme "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
rest "k8s.io/client-go/rest"
)
// ServersGetter has a method to return a ServerInterface.
// A group's client should implement this interface.
type ServersGetter interface {
Servers(namespace string) ServerInterface
}
// ServerInterface has methods to work with Server resources.
type ServerInterface interface {
Create(ctx context.Context, server *v1beta2.Server, opts v1.CreateOptions) (*v1beta2.Server, error)
Update(ctx context.Context, server *v1beta2.Server, opts v1.UpdateOptions) (*v1beta2.Server, 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) (*v1beta2.Server, error)
List(ctx context.Context, opts v1.ListOptions) (*v1beta2.ServerList, error)
Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta2.Server, err error)
ServerExpansion
}
// servers implements ServerInterface
type servers struct {
client rest.Interface
ns string
}
// newServers returns a Servers
func newServers(c *ServerV1beta2Client, namespace string) *servers {
return &servers{
client: c.RESTClient(),
ns: namespace,
}
}
// Get takes name of the server, and returns the corresponding server object, and an error if there is any.
func (c *servers) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta2.Server, err error) {
result = &v1beta2.Server{}
err = c.client.Get().
Namespace(c.ns).
Resource("servers").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do(ctx).
Into(result)
return
}
// List takes label and field selectors, and returns the list of Servers that match those selectors.
func (c *servers) List(ctx context.Context, opts v1.ListOptions) (result *v1beta2.ServerList, err error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
result = &v1beta2.ServerList{}
err = c.client.Get().
Namespace(c.ns).
Resource("servers").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Do(ctx).
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested servers.
func (c *servers) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
opts.Watch = true
return c.client.Get().
Namespace(c.ns).
Resource("servers").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Watch(ctx)
}
// Create takes the representation of a server and creates it. Returns the server's representation of the server, and an error, if there is any.
func (c *servers) Create(ctx context.Context, server *v1beta2.Server, opts v1.CreateOptions) (result *v1beta2.Server, err error) {
result = &v1beta2.Server{}
err = c.client.Post().
Namespace(c.ns).
Resource("servers").
VersionedParams(&opts, scheme.ParameterCodec).
Body(server).
Do(ctx).
Into(result)
return
}
// Update takes the representation of a server and updates it. Returns the server's representation of the server, and an error, if there is any.
func (c *servers) Update(ctx context.Context, server *v1beta2.Server, opts v1.UpdateOptions) (result *v1beta2.Server, err error) {
result = &v1beta2.Server{}
err = c.client.Put().
Namespace(c.ns).
Resource("servers").
Name(server.Name).
VersionedParams(&opts, scheme.ParameterCodec).
Body(server).
Do(ctx).
Into(result)
return
}
// Delete takes name of the server and deletes it. Returns an error if one occurs.
func (c *servers) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
return c.client.Delete().
Namespace(c.ns).
Resource("servers").
Name(name).
Body(&opts).
Do(ctx).
Error()
}
// DeleteCollection deletes a collection of objects.
func (c *servers) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
var timeout time.Duration
if listOpts.TimeoutSeconds != nil {
timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second
}
return c.client.Delete().
Namespace(c.ns).
Resource("servers").
VersionedParams(&listOpts, scheme.ParameterCodec).
Timeout(timeout).
Body(&opts).
Do(ctx).
Error()
}
// Patch applies the patch and returns the patched server.
func (c *servers) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta2.Server, err error) {
result = &v1beta2.Server{}
err = c.client.Patch(pt).
Namespace(c.ns).
Resource("servers").
Name(name).
SubResource(subresources...).
VersionedParams(&opts, scheme.ParameterCodec).
Body(data).
Do(ctx).
Into(result)
return
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package v1beta2
import (
"net/http"
v1beta2 "github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta2"
"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme"
rest "k8s.io/client-go/rest"
)
type ServerV1beta2Interface interface {
RESTClient() rest.Interface
ServersGetter
}
// ServerV1beta2Client is used to interact with features provided by the server group.
type ServerV1beta2Client struct {
restClient rest.Interface
}
func (c *ServerV1beta2Client) Servers(namespace string) ServerInterface {
return newServers(c, namespace)
}
// NewForConfig creates a new ServerV1beta2Client for the given config.
// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
// where httpClient was generated with rest.HTTPClientFor(c).
func NewForConfig(c *rest.Config) (*ServerV1beta2Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
httpClient, err := rest.HTTPClientFor(&config)
if err != nil {
return nil, err
}
return NewForConfigAndClient(&config, httpClient)
}
// NewForConfigAndClient creates a new ServerV1beta2Client for the given config and http client.
// Note the http client provided takes precedence over the configured transport values.
func NewForConfigAndClient(c *rest.Config, h *http.Client) (*ServerV1beta2Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
client, err := rest.RESTClientForConfigAndClient(&config, h)
if err != nil {
return nil, err
}
return &ServerV1beta2Client{client}, nil
}
// NewForConfigOrDie creates a new ServerV1beta2Client for the given config and
// panics if there is an error in the config.
func NewForConfigOrDie(c *rest.Config) *ServerV1beta2Client {
client, err := NewForConfig(c)
if err != nil {
panic(err)
}
return client
}
// New creates a new ServerV1beta2Client for the given RESTClient.
func New(c rest.Interface) *ServerV1beta2Client {
return &ServerV1beta2Client{c}
}
func setConfigDefaults(config *rest.Config) error {
gv := v1beta2.SchemeGroupVersion
config.GroupVersion = &gv
config.APIPath = "/apis"
config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
if config.UserAgent == "" {
config.UserAgent = rest.DefaultKubernetesUserAgent()
}
return nil
}
// RESTClient returns a RESTClient that is used to communicate
// with API server by this client implementation.
func (c *ServerV1beta2Client) RESTClient() rest.Interface {
if c == nil {
return nil
}
return c.restClient
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
"context"
v1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/serverauthorization/v1beta1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
)
// FakeServerAuthorizations implements ServerAuthorizationInterface
type FakeServerAuthorizations struct {
Fake *FakeServerauthorizationV1beta1
ns string
}
var serverauthorizationsResource = v1beta1.SchemeGroupVersion.WithResource("serverauthorizations")
var serverauthorizationsKind = v1beta1.SchemeGroupVersion.WithKind("ServerAuthorization")
// Get takes name of the serverAuthorization, and returns the corresponding serverAuthorization object, and an error if there is any.
func (c *FakeServerAuthorizations) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.ServerAuthorization, err error) {
obj, err := c.Fake.
Invokes(testing.NewGetAction(serverauthorizationsResource, c.ns, name), &v1beta1.ServerAuthorization{})
if obj == nil {
return nil, err
}
return obj.(*v1beta1.ServerAuthorization), err
}
// List takes label and field selectors, and returns the list of ServerAuthorizations that match those selectors.
func (c *FakeServerAuthorizations) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.ServerAuthorizationList, err error) {
obj, err := c.Fake.
Invokes(testing.NewListAction(serverauthorizationsResource, serverauthorizationsKind, c.ns, opts), &v1beta1.ServerAuthorizationList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &v1beta1.ServerAuthorizationList{ListMeta: obj.(*v1beta1.ServerAuthorizationList).ListMeta}
for _, item := range obj.(*v1beta1.ServerAuthorizationList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested serverAuthorizations.
func (c *FakeServerAuthorizations) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewWatchAction(serverauthorizationsResource, c.ns, opts))
}
// Create takes the representation of a serverAuthorization and creates it. Returns the server's representation of the serverAuthorization, and an error, if there is any.
func (c *FakeServerAuthorizations) Create(ctx context.Context, serverAuthorization *v1beta1.ServerAuthorization, opts v1.CreateOptions) (result *v1beta1.ServerAuthorization, err error) {
obj, err := c.Fake.
Invokes(testing.NewCreateAction(serverauthorizationsResource, c.ns, serverAuthorization), &v1beta1.ServerAuthorization{})
if obj == nil {
return nil, err
}
return obj.(*v1beta1.ServerAuthorization), err
}
// Update takes the representation of a serverAuthorization and updates it. Returns the server's representation of the serverAuthorization, and an error, if there is any.
func (c *FakeServerAuthorizations) Update(ctx context.Context, serverAuthorization *v1beta1.ServerAuthorization, opts v1.UpdateOptions) (result *v1beta1.ServerAuthorization, err error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateAction(serverauthorizationsResource, c.ns, serverAuthorization), &v1beta1.ServerAuthorization{})
if obj == nil {
return nil, err
}
return obj.(*v1beta1.ServerAuthorization), err
}
// Delete takes name of the serverAuthorization and deletes it. Returns an error if one occurs.
func (c *FakeServerAuthorizations) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewDeleteActionWithOptions(serverauthorizationsResource, c.ns, name, opts), &v1beta1.ServerAuthorization{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeServerAuthorizations) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
action := testing.NewDeleteCollectionAction(serverauthorizationsResource, c.ns, listOpts)
_, err := c.Fake.Invokes(action, &v1beta1.ServerAuthorizationList{})
return err
}
// Patch applies the patch and returns the patched serverAuthorization.
func (c *FakeServerAuthorizations) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.ServerAuthorization, err error) {
obj, err := c.Fake.
Invokes(testing.NewPatchSubresourceAction(serverauthorizationsResource, c.ns, name, pt, data, subresources...), &v1beta1.ServerAuthorization{})
if obj == nil {
return nil, err
}
return obj.(*v1beta1.ServerAuthorization), err
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
v1beta1 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/serverauthorization/v1beta1"
rest "k8s.io/client-go/rest"
testing "k8s.io/client-go/testing"
)
type FakeServerauthorizationV1beta1 struct {
*testing.Fake
}
func (c *FakeServerauthorizationV1beta1) ServerAuthorizations(namespace string) v1beta1.ServerAuthorizationInterface {
return &FakeServerAuthorizations{c, namespace}
}
// RESTClient returns a RESTClient that is used to communicate
// with API server by this client implementation.
func (c *FakeServerauthorizationV1beta1) RESTClient() rest.Interface {
var ret *rest.RESTClient
return ret
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package v1beta1
import (
"context"
"time"
v1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/serverauthorization/v1beta1"
scheme "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
rest "k8s.io/client-go/rest"
)
// ServerAuthorizationsGetter has a method to return a ServerAuthorizationInterface.
// A group's client should implement this interface.
type ServerAuthorizationsGetter interface {
ServerAuthorizations(namespace string) ServerAuthorizationInterface
}
// ServerAuthorizationInterface has methods to work with ServerAuthorization resources.
type ServerAuthorizationInterface interface {
Create(ctx context.Context, serverAuthorization *v1beta1.ServerAuthorization, opts v1.CreateOptions) (*v1beta1.ServerAuthorization, error)
Update(ctx context.Context, serverAuthorization *v1beta1.ServerAuthorization, opts v1.UpdateOptions) (*v1beta1.ServerAuthorization, 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) (*v1beta1.ServerAuthorization, error)
List(ctx context.Context, opts v1.ListOptions) (*v1beta1.ServerAuthorizationList, error)
Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.ServerAuthorization, err error)
ServerAuthorizationExpansion
}
// serverAuthorizations implements ServerAuthorizationInterface
type serverAuthorizations struct {
client rest.Interface
ns string
}
// newServerAuthorizations returns a ServerAuthorizations
func newServerAuthorizations(c *ServerauthorizationV1beta1Client, namespace string) *serverAuthorizations {
return &serverAuthorizations{
client: c.RESTClient(),
ns: namespace,
}
}
// Get takes name of the serverAuthorization, and returns the corresponding serverAuthorization object, and an error if there is any.
func (c *serverAuthorizations) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.ServerAuthorization, err error) {
result = &v1beta1.ServerAuthorization{}
err = c.client.Get().
Namespace(c.ns).
Resource("serverauthorizations").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do(ctx).
Into(result)
return
}
// List takes label and field selectors, and returns the list of ServerAuthorizations that match those selectors.
func (c *serverAuthorizations) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.ServerAuthorizationList, err error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
result = &v1beta1.ServerAuthorizationList{}
err = c.client.Get().
Namespace(c.ns).
Resource("serverauthorizations").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Do(ctx).
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested serverAuthorizations.
func (c *serverAuthorizations) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
opts.Watch = true
return c.client.Get().
Namespace(c.ns).
Resource("serverauthorizations").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Watch(ctx)
}
// Create takes the representation of a serverAuthorization and creates it. Returns the server's representation of the serverAuthorization, and an error, if there is any.
func (c *serverAuthorizations) Create(ctx context.Context, serverAuthorization *v1beta1.ServerAuthorization, opts v1.CreateOptions) (result *v1beta1.ServerAuthorization, err error) {
result = &v1beta1.ServerAuthorization{}
err = c.client.Post().
Namespace(c.ns).
Resource("serverauthorizations").
VersionedParams(&opts, scheme.ParameterCodec).
Body(serverAuthorization).
Do(ctx).
Into(result)
return
}
// Update takes the representation of a serverAuthorization and updates it. Returns the server's representation of the serverAuthorization, and an error, if there is any.
func (c *serverAuthorizations) Update(ctx context.Context, serverAuthorization *v1beta1.ServerAuthorization, opts v1.UpdateOptions) (result *v1beta1.ServerAuthorization, err error) {
result = &v1beta1.ServerAuthorization{}
err = c.client.Put().
Namespace(c.ns).
Resource("serverauthorizations").
Name(serverAuthorization.Name).
VersionedParams(&opts, scheme.ParameterCodec).
Body(serverAuthorization).
Do(ctx).
Into(result)
return
}
// Delete takes name of the serverAuthorization and deletes it. Returns an error if one occurs.
func (c *serverAuthorizations) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
return c.client.Delete().
Namespace(c.ns).
Resource("serverauthorizations").
Name(name).
Body(&opts).
Do(ctx).
Error()
}
// DeleteCollection deletes a collection of objects.
func (c *serverAuthorizations) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
var timeout time.Duration
if listOpts.TimeoutSeconds != nil {
timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second
}
return c.client.Delete().
Namespace(c.ns).
Resource("serverauthorizations").
VersionedParams(&listOpts, scheme.ParameterCodec).
Timeout(timeout).
Body(&opts).
Do(ctx).
Error()
}
// Patch applies the patch and returns the patched serverAuthorization.
func (c *serverAuthorizations) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.ServerAuthorization, err error) {
result = &v1beta1.ServerAuthorization{}
err = c.client.Patch(pt).
Namespace(c.ns).
Resource("serverauthorizations").
Name(name).
SubResource(subresources...).
VersionedParams(&opts, scheme.ParameterCodec).
Body(data).
Do(ctx).
Into(result)
return
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package v1beta1
import (
"net/http"
v1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/serverauthorization/v1beta1"
"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme"
rest "k8s.io/client-go/rest"
)
type ServerauthorizationV1beta1Interface interface {
RESTClient() rest.Interface
ServerAuthorizationsGetter
}
// ServerauthorizationV1beta1Client is used to interact with features provided by the serverauthorization group.
type ServerauthorizationV1beta1Client struct {
restClient rest.Interface
}
func (c *ServerauthorizationV1beta1Client) ServerAuthorizations(namespace string) ServerAuthorizationInterface {
return newServerAuthorizations(c, namespace)
}
// NewForConfig creates a new ServerauthorizationV1beta1Client for the given config.
// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
// where httpClient was generated with rest.HTTPClientFor(c).
func NewForConfig(c *rest.Config) (*ServerauthorizationV1beta1Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
httpClient, err := rest.HTTPClientFor(&config)
if err != nil {
return nil, err
}
return NewForConfigAndClient(&config, httpClient)
}
// NewForConfigAndClient creates a new ServerauthorizationV1beta1Client for the given config and http client.
// Note the http client provided takes precedence over the configured transport values.
func NewForConfigAndClient(c *rest.Config, h *http.Client) (*ServerauthorizationV1beta1Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
client, err := rest.RESTClientForConfigAndClient(&config, h)
if err != nil {
return nil, err
}
return &ServerauthorizationV1beta1Client{client}, nil
}
// NewForConfigOrDie creates a new ServerauthorizationV1beta1Client for the given config and
// panics if there is an error in the config.
func NewForConfigOrDie(c *rest.Config) *ServerauthorizationV1beta1Client {
client, err := NewForConfig(c)
if err != nil {
panic(err)
}
return client
}
// New creates a new ServerauthorizationV1beta1Client for the given RESTClient.
func New(c rest.Interface) *ServerauthorizationV1beta1Client {
return &ServerauthorizationV1beta1Client{c}
}
func setConfigDefaults(config *rest.Config) error {
gv := v1beta1.SchemeGroupVersion
config.GroupVersion = &gv
config.APIPath = "/apis"
config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
if config.UserAgent == "" {
config.UserAgent = rest.DefaultKubernetesUserAgent()
}
return nil
}
// RESTClient returns a RESTClient that is used to communicate
// with API server by this client implementation.
func (c *ServerauthorizationV1beta1Client) RESTClient() rest.Interface {
if c == nil {
return nil
}
return c.restClient
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
"context"
v1alpha2 "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
)
// FakeServiceProfiles implements ServiceProfileInterface
type FakeServiceProfiles struct {
Fake *FakeLinkerdV1alpha2
ns string
}
var serviceprofilesResource = v1alpha2.SchemeGroupVersion.WithResource("serviceprofiles")
var serviceprofilesKind = v1alpha2.SchemeGroupVersion.WithKind("ServiceProfile")
// Get takes name of the serviceProfile, and returns the corresponding serviceProfile object, and an error if there is any.
func (c *FakeServiceProfiles) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha2.ServiceProfile, err error) {
obj, err := c.Fake.
Invokes(testing.NewGetAction(serviceprofilesResource, c.ns, name), &v1alpha2.ServiceProfile{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha2.ServiceProfile), err
}
// List takes label and field selectors, and returns the list of ServiceProfiles that match those selectors.
func (c *FakeServiceProfiles) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha2.ServiceProfileList, err error) {
obj, err := c.Fake.
Invokes(testing.NewListAction(serviceprofilesResource, serviceprofilesKind, c.ns, opts), &v1alpha2.ServiceProfileList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &v1alpha2.ServiceProfileList{ListMeta: obj.(*v1alpha2.ServiceProfileList).ListMeta}
for _, item := range obj.(*v1alpha2.ServiceProfileList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested serviceProfiles.
func (c *FakeServiceProfiles) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewWatchAction(serviceprofilesResource, c.ns, opts))
}
// Create takes the representation of a serviceProfile and creates it. Returns the server's representation of the serviceProfile, and an error, if there is any.
func (c *FakeServiceProfiles) Create(ctx context.Context, serviceProfile *v1alpha2.ServiceProfile, opts v1.CreateOptions) (result *v1alpha2.ServiceProfile, err error) {
obj, err := c.Fake.
Invokes(testing.NewCreateAction(serviceprofilesResource, c.ns, serviceProfile), &v1alpha2.ServiceProfile{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha2.ServiceProfile), err
}
// Update takes the representation of a serviceProfile and updates it. Returns the server's representation of the serviceProfile, and an error, if there is any.
func (c *FakeServiceProfiles) Update(ctx context.Context, serviceProfile *v1alpha2.ServiceProfile, opts v1.UpdateOptions) (result *v1alpha2.ServiceProfile, err error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateAction(serviceprofilesResource, c.ns, serviceProfile), &v1alpha2.ServiceProfile{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha2.ServiceProfile), err
}
// Delete takes name of the serviceProfile and deletes it. Returns an error if one occurs.
func (c *FakeServiceProfiles) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewDeleteActionWithOptions(serviceprofilesResource, c.ns, name, opts), &v1alpha2.ServiceProfile{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeServiceProfiles) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
action := testing.NewDeleteCollectionAction(serviceprofilesResource, c.ns, listOpts)
_, err := c.Fake.Invokes(action, &v1alpha2.ServiceProfileList{})
return err
}
// Patch applies the patch and returns the patched serviceProfile.
func (c *FakeServiceProfiles) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.ServiceProfile, err error) {
obj, err := c.Fake.
Invokes(testing.NewPatchSubresourceAction(serviceprofilesResource, c.ns, name, pt, data, subresources...), &v1alpha2.ServiceProfile{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha2.ServiceProfile), err
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
v1alpha2 "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/serviceprofile/v1alpha2"
rest "k8s.io/client-go/rest"
testing "k8s.io/client-go/testing"
)
type FakeLinkerdV1alpha2 struct {
*testing.Fake
}
func (c *FakeLinkerdV1alpha2) ServiceProfiles(namespace string) v1alpha2.ServiceProfileInterface {
return &FakeServiceProfiles{c, namespace}
}
// RESTClient returns a RESTClient that is used to communicate
// with API server by this client implementation.
func (c *FakeLinkerdV1alpha2) RESTClient() rest.Interface {
var ret *rest.RESTClient
return ret
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package v1alpha2
import (
"context"
"time"
v1alpha2 "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
scheme "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
rest "k8s.io/client-go/rest"
)
// ServiceProfilesGetter has a method to return a ServiceProfileInterface.
// A group's client should implement this interface.
type ServiceProfilesGetter interface {
ServiceProfiles(namespace string) ServiceProfileInterface
}
// ServiceProfileInterface has methods to work with ServiceProfile resources.
type ServiceProfileInterface interface {
Create(ctx context.Context, serviceProfile *v1alpha2.ServiceProfile, opts v1.CreateOptions) (*v1alpha2.ServiceProfile, error)
Update(ctx context.Context, serviceProfile *v1alpha2.ServiceProfile, opts v1.UpdateOptions) (*v1alpha2.ServiceProfile, 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) (*v1alpha2.ServiceProfile, error)
List(ctx context.Context, opts v1.ListOptions) (*v1alpha2.ServiceProfileList, error)
Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.ServiceProfile, err error)
ServiceProfileExpansion
}
// serviceProfiles implements ServiceProfileInterface
type serviceProfiles struct {
client rest.Interface
ns string
}
// newServiceProfiles returns a ServiceProfiles
func newServiceProfiles(c *LinkerdV1alpha2Client, namespace string) *serviceProfiles {
return &serviceProfiles{
client: c.RESTClient(),
ns: namespace,
}
}
// Get takes name of the serviceProfile, and returns the corresponding serviceProfile object, and an error if there is any.
func (c *serviceProfiles) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha2.ServiceProfile, err error) {
result = &v1alpha2.ServiceProfile{}
err = c.client.Get().
Namespace(c.ns).
Resource("serviceprofiles").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do(ctx).
Into(result)
return
}
// List takes label and field selectors, and returns the list of ServiceProfiles that match those selectors.
func (c *serviceProfiles) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha2.ServiceProfileList, err error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
result = &v1alpha2.ServiceProfileList{}
err = c.client.Get().
Namespace(c.ns).
Resource("serviceprofiles").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Do(ctx).
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested serviceProfiles.
func (c *serviceProfiles) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
opts.Watch = true
return c.client.Get().
Namespace(c.ns).
Resource("serviceprofiles").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Watch(ctx)
}
// Create takes the representation of a serviceProfile and creates it. Returns the server's representation of the serviceProfile, and an error, if there is any.
func (c *serviceProfiles) Create(ctx context.Context, serviceProfile *v1alpha2.ServiceProfile, opts v1.CreateOptions) (result *v1alpha2.ServiceProfile, err error) {
result = &v1alpha2.ServiceProfile{}
err = c.client.Post().
Namespace(c.ns).
Resource("serviceprofiles").
VersionedParams(&opts, scheme.ParameterCodec).
Body(serviceProfile).
Do(ctx).
Into(result)
return
}
// Update takes the representation of a serviceProfile and updates it. Returns the server's representation of the serviceProfile, and an error, if there is any.
func (c *serviceProfiles) Update(ctx context.Context, serviceProfile *v1alpha2.ServiceProfile, opts v1.UpdateOptions) (result *v1alpha2.ServiceProfile, err error) {
result = &v1alpha2.ServiceProfile{}
err = c.client.Put().
Namespace(c.ns).
Resource("serviceprofiles").
Name(serviceProfile.Name).
VersionedParams(&opts, scheme.ParameterCodec).
Body(serviceProfile).
Do(ctx).
Into(result)
return
}
// Delete takes name of the serviceProfile and deletes it. Returns an error if one occurs.
func (c *serviceProfiles) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
return c.client.Delete().
Namespace(c.ns).
Resource("serviceprofiles").
Name(name).
Body(&opts).
Do(ctx).
Error()
}
// DeleteCollection deletes a collection of objects.
func (c *serviceProfiles) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
var timeout time.Duration
if listOpts.TimeoutSeconds != nil {
timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second
}
return c.client.Delete().
Namespace(c.ns).
Resource("serviceprofiles").
VersionedParams(&listOpts, scheme.ParameterCodec).
Timeout(timeout).
Body(&opts).
Do(ctx).
Error()
}
// Patch applies the patch and returns the patched serviceProfile.
func (c *serviceProfiles) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.ServiceProfile, err error) {
result = &v1alpha2.ServiceProfile{}
err = c.client.Patch(pt).
Namespace(c.ns).
Resource("serviceprofiles").
Name(name).
SubResource(subresources...).
VersionedParams(&opts, scheme.ParameterCodec).
Body(data).
Do(ctx).
Into(result)
return
}
/*
Copyright 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package v1alpha2
import (
"net/http"
v1alpha2 "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme"
rest "k8s.io/client-go/rest"
)
type LinkerdV1alpha2Interface interface {
RESTClient() rest.Interface
ServiceProfilesGetter
}
// LinkerdV1alpha2Client is used to interact with features provided by the linkerd.io group.
type LinkerdV1alpha2Client struct {
restClient rest.Interface
}
func (c *LinkerdV1alpha2Client) ServiceProfiles(namespace string) ServiceProfileInterface {
return newServiceProfiles(c, namespace)
}
// NewForConfig creates a new LinkerdV1alpha2Client for the given config.
// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
// where httpClient was generated with rest.HTTPClientFor(c).
func NewForConfig(c *rest.Config) (*LinkerdV1alpha2Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
httpClient, err := rest.HTTPClientFor(&config)
if err != nil {
return nil, err
}
return NewForConfigAndClient(&config, httpClient)
}
// NewForConfigAndClient creates a new LinkerdV1alpha2Client for the given config and http client.
// Note the http client provided takes precedence over the configured transport values.
func NewForConfigAndClient(c *rest.Config, h *http.Client) (*LinkerdV1alpha2Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
client, err := rest.RESTClientForConfigAndClient(&config, h)
if err != nil {
return nil, err
}
return &LinkerdV1alpha2Client{client}, nil
}
// NewForConfigOrDie creates a new LinkerdV1alpha2Client for the given config and
// panics if there is an error in the config.
func NewForConfigOrDie(c *rest.Config) *LinkerdV1alpha2Client {
client, err := NewForConfig(c)
if err != nil {
panic(err)
}
return client
}
// New creates a new LinkerdV1alpha2Client for the given RESTClient.
func New(c rest.Interface) *LinkerdV1alpha2Client {
return &LinkerdV1alpha2Client{c}
}
func setConfigDefaults(config *rest.Config) error {
gv := v1alpha2.SchemeGroupVersion
config.GroupVersion = &gv
config.APIPath = "/apis"
config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
if config.UserAgent == "" {
config.UserAgent = rest.DefaultKubernetesUserAgent()
}
return nil
}
// RESTClient returns a RESTClient that is used to communicate
// with API server by this client implementation.
func (c *LinkerdV1alpha2Client) RESTClient() rest.Interface {
if c == nil {
return nil
}
return c.restClient
}
/*
Copyright 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.
*/
// Code generated by informer-gen. DO NOT EDIT.
package externalworkload
import (
v1beta1 "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/externalworkload/v1beta1"
internalinterfaces "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces"
)
// Interface provides access to each of this group's versions.
type Interface interface {
// V1beta1 provides access to shared informers for resources in V1beta1.
V1beta1() v1beta1.Interface
}
type group struct {
factory internalinterfaces.SharedInformerFactory
namespace string
tweakListOptions internalinterfaces.TweakListOptionsFunc
}
// New returns a new Interface.
func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// V1beta1 returns a new v1beta1.Interface.
func (g *group) V1beta1() v1beta1.Interface {
return v1beta1.New(g.factory, g.namespace, g.tweakListOptions)
}
/*
Copyright 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.
*/
// Code generated by informer-gen. DO NOT EDIT.
package v1beta1
import (
"context"
time "time"
externalworkloadv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1"
versioned "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned"
internalinterfaces "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces"
v1beta1 "github.com/linkerd/linkerd2/controller/gen/client/listers/externalworkload/v1beta1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
cache "k8s.io/client-go/tools/cache"
)
// ExternalWorkloadInformer provides access to a shared informer and lister for
// ExternalWorkloads.
type ExternalWorkloadInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1beta1.ExternalWorkloadLister
}
type externalWorkloadInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
namespace string
}
// NewExternalWorkloadInformer constructs a new informer for ExternalWorkload type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewExternalWorkloadInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredExternalWorkloadInformer(client, namespace, resyncPeriod, indexers, nil)
}
// NewFilteredExternalWorkloadInformer constructs a new informer for ExternalWorkload type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewFilteredExternalWorkloadInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.ExternalworkloadV1beta1().ExternalWorkloads(namespace).List(context.TODO(), options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.ExternalworkloadV1beta1().ExternalWorkloads(namespace).Watch(context.TODO(), options)
},
},
&externalworkloadv1beta1.ExternalWorkload{},
resyncPeriod,
indexers,
)
}
func (f *externalWorkloadInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredExternalWorkloadInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *externalWorkloadInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&externalworkloadv1beta1.ExternalWorkload{}, f.defaultInformer)
}
func (f *externalWorkloadInformer) Lister() v1beta1.ExternalWorkloadLister {
return v1beta1.NewExternalWorkloadLister(f.Informer().GetIndexer())
}
/*
Copyright 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.
*/
// Code generated by informer-gen. DO NOT EDIT.
package v1beta1
import (
internalinterfaces "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces"
)
// Interface provides access to all the informers in this group version.
type Interface interface {
// ExternalWorkloads returns a ExternalWorkloadInformer.
ExternalWorkloads() ExternalWorkloadInformer
}
type version struct {
factory internalinterfaces.SharedInformerFactory
namespace string
tweakListOptions internalinterfaces.TweakListOptionsFunc
}
// New returns a new Interface.
func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// ExternalWorkloads returns a ExternalWorkloadInformer.
func (v *version) ExternalWorkloads() ExternalWorkloadInformer {
return &externalWorkloadInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
/*
Copyright 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.
*/
// Code generated by informer-gen. DO NOT EDIT.
package externalversions
import (
reflect "reflect"
sync "sync"
time "time"
versioned "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned"
externalworkload "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/externalworkload"
internalinterfaces "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces"
link "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/link"
policy "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/policy"
server "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/server"
serverauthorization "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/serverauthorization"
serviceprofile "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/serviceprofile"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
schema "k8s.io/apimachinery/pkg/runtime/schema"
cache "k8s.io/client-go/tools/cache"
)
// SharedInformerOption defines the functional option type for SharedInformerFactory.
type SharedInformerOption func(*sharedInformerFactory) *sharedInformerFactory
type sharedInformerFactory struct {
client versioned.Interface
namespace string
tweakListOptions internalinterfaces.TweakListOptionsFunc
lock sync.Mutex
defaultResync time.Duration
customResync map[reflect.Type]time.Duration
transform cache.TransformFunc
informers map[reflect.Type]cache.SharedIndexInformer
// startedInformers is used for tracking which informers have been started.
// This allows Start() to be called multiple times safely.
startedInformers map[reflect.Type]bool
// wg tracks how many goroutines were started.
wg sync.WaitGroup
// shuttingDown is true when Shutdown has been called. It may still be running
// because it needs to wait for goroutines.
shuttingDown bool
}
// WithCustomResyncConfig sets a custom resync period for the specified informer types.
func WithCustomResyncConfig(resyncConfig map[v1.Object]time.Duration) SharedInformerOption {
return func(factory *sharedInformerFactory) *sharedInformerFactory {
for k, v := range resyncConfig {
factory.customResync[reflect.TypeOf(k)] = v
}
return factory
}
}
// WithTweakListOptions sets a custom filter on all listers of the configured SharedInformerFactory.
func WithTweakListOptions(tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerOption {
return func(factory *sharedInformerFactory) *sharedInformerFactory {
factory.tweakListOptions = tweakListOptions
return factory
}
}
// WithNamespace limits the SharedInformerFactory to the specified namespace.
func WithNamespace(namespace string) SharedInformerOption {
return func(factory *sharedInformerFactory) *sharedInformerFactory {
factory.namespace = namespace
return factory
}
}
// WithTransform sets a transform on all informers.
func WithTransform(transform cache.TransformFunc) SharedInformerOption {
return func(factory *sharedInformerFactory) *sharedInformerFactory {
factory.transform = transform
return factory
}
}
// NewSharedInformerFactory constructs a new instance of sharedInformerFactory for all namespaces.
func NewSharedInformerFactory(client versioned.Interface, defaultResync time.Duration) SharedInformerFactory {
return NewSharedInformerFactoryWithOptions(client, defaultResync)
}
// NewFilteredSharedInformerFactory constructs a new instance of sharedInformerFactory.
// Listers obtained via this SharedInformerFactory will be subject to the same filters
// as specified here.
// Deprecated: Please use NewSharedInformerFactoryWithOptions instead
func NewFilteredSharedInformerFactory(client versioned.Interface, defaultResync time.Duration, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerFactory {
return NewSharedInformerFactoryWithOptions(client, defaultResync, WithNamespace(namespace), WithTweakListOptions(tweakListOptions))
}
// NewSharedInformerFactoryWithOptions constructs a new instance of a SharedInformerFactory with additional options.
func NewSharedInformerFactoryWithOptions(client versioned.Interface, defaultResync time.Duration, options ...SharedInformerOption) SharedInformerFactory {
factory := &sharedInformerFactory{
client: client,
namespace: v1.NamespaceAll,
defaultResync: defaultResync,
informers: make(map[reflect.Type]cache.SharedIndexInformer),
startedInformers: make(map[reflect.Type]bool),
customResync: make(map[reflect.Type]time.Duration),
}
// Apply all options
for _, opt := range options {
factory = opt(factory)
}
return factory
}
func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) {
f.lock.Lock()
defer f.lock.Unlock()
if f.shuttingDown {
return
}
for informerType, informer := range f.informers {
if !f.startedInformers[informerType] {
f.wg.Add(1)
// We need a new variable in each loop iteration,
// otherwise the goroutine would use the loop variable
// and that keeps changing.
informer := informer
go func() {
defer f.wg.Done()
informer.Run(stopCh)
}()
f.startedInformers[informerType] = true
}
}
}
func (f *sharedInformerFactory) Shutdown() {
f.lock.Lock()
f.shuttingDown = true
f.lock.Unlock()
// Will return immediately if there is nothing to wait for.
f.wg.Wait()
}
func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool {
informers := func() map[reflect.Type]cache.SharedIndexInformer {
f.lock.Lock()
defer f.lock.Unlock()
informers := map[reflect.Type]cache.SharedIndexInformer{}
for informerType, informer := range f.informers {
if f.startedInformers[informerType] {
informers[informerType] = informer
}
}
return informers
}()
res := map[reflect.Type]bool{}
for informType, informer := range informers {
res[informType] = cache.WaitForCacheSync(stopCh, informer.HasSynced)
}
return res
}
// InformerFor returns the SharedIndexInformer for obj using an internal
// client.
func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer {
f.lock.Lock()
defer f.lock.Unlock()
informerType := reflect.TypeOf(obj)
informer, exists := f.informers[informerType]
if exists {
return informer
}
resyncPeriod, exists := f.customResync[informerType]
if !exists {
resyncPeriod = f.defaultResync
}
informer = newFunc(f.client, resyncPeriod)
informer.SetTransform(f.transform)
f.informers[informerType] = informer
return informer
}
// SharedInformerFactory provides shared informers for resources in all known
// API group versions.
//
// It is typically used like this:
//
// ctx, cancel := context.Background()
// defer cancel()
// factory := NewSharedInformerFactory(client, resyncPeriod)
// defer factory.WaitForStop() // Returns immediately if nothing was started.
// genericInformer := factory.ForResource(resource)
// typedInformer := factory.SomeAPIGroup().V1().SomeType()
// factory.Start(ctx.Done()) // Start processing these informers.
// synced := factory.WaitForCacheSync(ctx.Done())
// for v, ok := range synced {
// if !ok {
// fmt.Fprintf(os.Stderr, "caches failed to sync: %v", v)
// return
// }
// }
//
// // Creating informers can also be created after Start, but then
// // Start must be called again:
// anotherGenericInformer := factory.ForResource(resource)
// factory.Start(ctx.Done())
type SharedInformerFactory interface {
internalinterfaces.SharedInformerFactory
// Start initializes all requested informers. They are handled in goroutines
// which run until the stop channel gets closed.
Start(stopCh <-chan struct{})
// Shutdown marks a factory as shutting down. At that point no new
// informers can be started anymore and Start will return without
// doing anything.
//
// In addition, Shutdown blocks until all goroutines have terminated. For that
// to happen, the close channel(s) that they were started with must be closed,
// either before Shutdown gets called or while it is waiting.
//
// Shutdown may be called multiple times, even concurrently. All such calls will
// block until all goroutines have terminated.
Shutdown()
// WaitForCacheSync blocks until all started informers' caches were synced
// or the stop channel gets closed.
WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool
// ForResource gives generic access to a shared informer of the matching type.
ForResource(resource schema.GroupVersionResource) (GenericInformer, error)
// InformerFor returns the SharedIndexInformer for obj using an internal
// client.
InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer
Externalworkload() externalworkload.Interface
Link() link.Interface
Policy() policy.Interface
Server() server.Interface
Serverauthorization() serverauthorization.Interface
Linkerd() serviceprofile.Interface
}
func (f *sharedInformerFactory) Externalworkload() externalworkload.Interface {
return externalworkload.New(f, f.namespace, f.tweakListOptions)
}
func (f *sharedInformerFactory) Link() link.Interface {
return link.New(f, f.namespace, f.tweakListOptions)
}
func (f *sharedInformerFactory) Policy() policy.Interface {
return policy.New(f, f.namespace, f.tweakListOptions)
}
func (f *sharedInformerFactory) Server() server.Interface {
return server.New(f, f.namespace, f.tweakListOptions)
}
func (f *sharedInformerFactory) Serverauthorization() serverauthorization.Interface {
return serverauthorization.New(f, f.namespace, f.tweakListOptions)
}
func (f *sharedInformerFactory) Linkerd() serviceprofile.Interface {
return serviceprofile.New(f, f.namespace, f.tweakListOptions)
}
/*
Copyright 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.
*/
// Code generated by informer-gen. DO NOT EDIT.
package externalversions
import (
"fmt"
v1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1"
v1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha1"
policyv1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1"
v1beta3 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1beta3"
serverv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta1"
v1beta2 "github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta2"
serverauthorizationv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/serverauthorization/v1beta1"
v1alpha2 "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
schema "k8s.io/apimachinery/pkg/runtime/schema"
cache "k8s.io/client-go/tools/cache"
)
// GenericInformer is type of SharedIndexInformer which will locate and delegate to other
// sharedInformers based on type
type GenericInformer interface {
Informer() cache.SharedIndexInformer
Lister() cache.GenericLister
}
type genericInformer struct {
informer cache.SharedIndexInformer
resource schema.GroupResource
}
// Informer returns the SharedIndexInformer.
func (f *genericInformer) Informer() cache.SharedIndexInformer {
return f.informer
}
// Lister returns the GenericLister.
func (f *genericInformer) Lister() cache.GenericLister {
return cache.NewGenericLister(f.Informer().GetIndexer(), f.resource)
}
// ForResource gives generic access to a shared informer of the matching type
// TODO extend this to unknown resources with a client pool
func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) {
switch resource {
// Group=externalworkload, Version=v1beta1
case v1beta1.SchemeGroupVersion.WithResource("externalworkloads"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Externalworkload().V1beta1().ExternalWorkloads().Informer()}, nil
// Group=link, Version=v1alpha1
case v1alpha1.SchemeGroupVersion.WithResource("links"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Link().V1alpha1().Links().Informer()}, nil
// Group=linkerd.io, Version=v1alpha2
case v1alpha2.SchemeGroupVersion.WithResource("serviceprofiles"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Linkerd().V1alpha2().ServiceProfiles().Informer()}, nil
// Group=policy, Version=v1alpha1
case policyv1alpha1.SchemeGroupVersion.WithResource("authorizationpolicies"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Policy().V1alpha1().AuthorizationPolicies().Informer()}, nil
case policyv1alpha1.SchemeGroupVersion.WithResource("httproutes"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Policy().V1alpha1().HTTPRoutes().Informer()}, nil
case policyv1alpha1.SchemeGroupVersion.WithResource("meshtlsauthentications"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Policy().V1alpha1().MeshTLSAuthentications().Informer()}, nil
case policyv1alpha1.SchemeGroupVersion.WithResource("networkauthentications"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Policy().V1alpha1().NetworkAuthentications().Informer()}, nil
// Group=policy, Version=v1beta3
case v1beta3.SchemeGroupVersion.WithResource("httproutes"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Policy().V1beta3().HTTPRoutes().Informer()}, nil
// Group=server, Version=v1beta1
case serverv1beta1.SchemeGroupVersion.WithResource("servers"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Server().V1beta1().Servers().Informer()}, nil
// Group=server, Version=v1beta2
case v1beta2.SchemeGroupVersion.WithResource("servers"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Server().V1beta2().Servers().Informer()}, nil
// Group=serverauthorization, Version=v1beta1
case serverauthorizationv1beta1.SchemeGroupVersion.WithResource("serverauthorizations"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Serverauthorization().V1beta1().ServerAuthorizations().Informer()}, nil
}
return nil, fmt.Errorf("no informer found for %v", resource)
}
/*
Copyright 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.
*/
// Code generated by informer-gen. DO NOT EDIT.
package link
import (
internalinterfaces "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces"
v1alpha1 "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/link/v1alpha1"
)
// Interface provides access to each of this group's versions.
type Interface interface {
// V1alpha1 provides access to shared informers for resources in V1alpha1.
V1alpha1() v1alpha1.Interface
}
type group struct {
factory internalinterfaces.SharedInformerFactory
namespace string
tweakListOptions internalinterfaces.TweakListOptionsFunc
}
// New returns a new Interface.
func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// V1alpha1 returns a new v1alpha1.Interface.
func (g *group) V1alpha1() v1alpha1.Interface {
return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions)
}
/*
Copyright 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.
*/
// Code generated by informer-gen. DO NOT EDIT.
package v1alpha1
import (
internalinterfaces "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces"
)
// Interface provides access to all the informers in this group version.
type Interface interface {
// Links returns a LinkInformer.
Links() LinkInformer
}
type version struct {
factory internalinterfaces.SharedInformerFactory
namespace string
tweakListOptions internalinterfaces.TweakListOptionsFunc
}
// New returns a new Interface.
func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// Links returns a LinkInformer.
func (v *version) Links() LinkInformer {
return &linkInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
/*
Copyright 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.
*/
// Code generated by informer-gen. DO NOT EDIT.
package v1alpha1
import (
"context"
time "time"
linkv1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha1"
versioned "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned"
internalinterfaces "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces"
v1alpha1 "github.com/linkerd/linkerd2/controller/gen/client/listers/link/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
cache "k8s.io/client-go/tools/cache"
)
// LinkInformer provides access to a shared informer and lister for
// Links.
type LinkInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1alpha1.LinkLister
}
type linkInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
namespace string
}
// NewLinkInformer constructs a new informer for Link type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewLinkInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredLinkInformer(client, namespace, resyncPeriod, indexers, nil)
}
// NewFilteredLinkInformer constructs a new informer for Link type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewFilteredLinkInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.LinkV1alpha1().Links(namespace).List(context.TODO(), options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.LinkV1alpha1().Links(namespace).Watch(context.TODO(), options)
},
},
&linkv1alpha1.Link{},
resyncPeriod,
indexers,
)
}
func (f *linkInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredLinkInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *linkInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&linkv1alpha1.Link{}, f.defaultInformer)
}
func (f *linkInformer) Lister() v1alpha1.LinkLister {
return v1alpha1.NewLinkLister(f.Informer().GetIndexer())
}
/*
Copyright 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.
*/
// Code generated by informer-gen. DO NOT EDIT.
package policy
import (
internalinterfaces "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces"
v1alpha1 "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/policy/v1alpha1"
v1beta3 "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/policy/v1beta3"
)
// Interface provides access to each of this group's versions.
type Interface interface {
// V1alpha1 provides access to shared informers for resources in V1alpha1.
V1alpha1() v1alpha1.Interface
// V1beta3 provides access to shared informers for resources in V1beta3.
V1beta3() v1beta3.Interface
}
type group struct {
factory internalinterfaces.SharedInformerFactory
namespace string
tweakListOptions internalinterfaces.TweakListOptionsFunc
}
// New returns a new Interface.
func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// V1alpha1 returns a new v1alpha1.Interface.
func (g *group) V1alpha1() v1alpha1.Interface {
return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions)
}
// V1beta3 returns a new v1beta3.Interface.
func (g *group) V1beta3() v1beta3.Interface {
return v1beta3.New(g.factory, g.namespace, g.tweakListOptions)
}
/*
Copyright 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.
*/
// Code generated by informer-gen. DO NOT EDIT.
package v1alpha1
import (
"context"
time "time"
policyv1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1"
versioned "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned"
internalinterfaces "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces"
v1alpha1 "github.com/linkerd/linkerd2/controller/gen/client/listers/policy/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
cache "k8s.io/client-go/tools/cache"
)
// AuthorizationPolicyInformer provides access to a shared informer and lister for
// AuthorizationPolicies.
type AuthorizationPolicyInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1alpha1.AuthorizationPolicyLister
}
type authorizationPolicyInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
namespace string
}
// NewAuthorizationPolicyInformer constructs a new informer for AuthorizationPolicy type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewAuthorizationPolicyInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredAuthorizationPolicyInformer(client, namespace, resyncPeriod, indexers, nil)
}
// NewFilteredAuthorizationPolicyInformer constructs a new informer for AuthorizationPolicy type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewFilteredAuthorizationPolicyInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.PolicyV1alpha1().AuthorizationPolicies(namespace).List(context.TODO(), options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.PolicyV1alpha1().AuthorizationPolicies(namespace).Watch(context.TODO(), options)
},
},
&policyv1alpha1.AuthorizationPolicy{},
resyncPeriod,
indexers,
)
}
func (f *authorizationPolicyInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredAuthorizationPolicyInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *authorizationPolicyInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&policyv1alpha1.AuthorizationPolicy{}, f.defaultInformer)
}
func (f *authorizationPolicyInformer) Lister() v1alpha1.AuthorizationPolicyLister {
return v1alpha1.NewAuthorizationPolicyLister(f.Informer().GetIndexer())
}
/*
Copyright 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.
*/
// Code generated by informer-gen. DO NOT EDIT.
package v1alpha1
import (
"context"
time "time"
policyv1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1"
versioned "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned"
internalinterfaces "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces"
v1alpha1 "github.com/linkerd/linkerd2/controller/gen/client/listers/policy/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
cache "k8s.io/client-go/tools/cache"
)
// HTTPRouteInformer provides access to a shared informer and lister for
// HTTPRoutes.
type HTTPRouteInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1alpha1.HTTPRouteLister
}
type hTTPRouteInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
namespace string
}
// NewHTTPRouteInformer constructs a new informer for HTTPRoute type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewHTTPRouteInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredHTTPRouteInformer(client, namespace, resyncPeriod, indexers, nil)
}
// NewFilteredHTTPRouteInformer constructs a new informer for HTTPRoute type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewFilteredHTTPRouteInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.PolicyV1alpha1().HTTPRoutes(namespace).List(context.TODO(), options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.PolicyV1alpha1().HTTPRoutes(namespace).Watch(context.TODO(), options)
},
},
&policyv1alpha1.HTTPRoute{},
resyncPeriod,
indexers,
)
}
func (f *hTTPRouteInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredHTTPRouteInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *hTTPRouteInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&policyv1alpha1.HTTPRoute{}, f.defaultInformer)
}
func (f *hTTPRouteInformer) Lister() v1alpha1.HTTPRouteLister {
return v1alpha1.NewHTTPRouteLister(f.Informer().GetIndexer())
}
/*
Copyright 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.
*/
// Code generated by informer-gen. DO NOT EDIT.
package v1alpha1
import (
internalinterfaces "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces"
)
// Interface provides access to all the informers in this group version.
type Interface interface {
// AuthorizationPolicies returns a AuthorizationPolicyInformer.
AuthorizationPolicies() AuthorizationPolicyInformer
// HTTPRoutes returns a HTTPRouteInformer.
HTTPRoutes() HTTPRouteInformer
// MeshTLSAuthentications returns a MeshTLSAuthenticationInformer.
MeshTLSAuthentications() MeshTLSAuthenticationInformer
// NetworkAuthentications returns a NetworkAuthenticationInformer.
NetworkAuthentications() NetworkAuthenticationInformer
}
type version struct {
factory internalinterfaces.SharedInformerFactory
namespace string
tweakListOptions internalinterfaces.TweakListOptionsFunc
}
// New returns a new Interface.
func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// AuthorizationPolicies returns a AuthorizationPolicyInformer.
func (v *version) AuthorizationPolicies() AuthorizationPolicyInformer {
return &authorizationPolicyInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
// HTTPRoutes returns a HTTPRouteInformer.
func (v *version) HTTPRoutes() HTTPRouteInformer {
return &hTTPRouteInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
// MeshTLSAuthentications returns a MeshTLSAuthenticationInformer.
func (v *version) MeshTLSAuthentications() MeshTLSAuthenticationInformer {
return &meshTLSAuthenticationInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
// NetworkAuthentications returns a NetworkAuthenticationInformer.
func (v *version) NetworkAuthentications() NetworkAuthenticationInformer {
return &networkAuthenticationInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
/*
Copyright 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.
*/
// Code generated by informer-gen. DO NOT EDIT.
package v1alpha1
import (
"context"
time "time"
policyv1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1"
versioned "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned"
internalinterfaces "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces"
v1alpha1 "github.com/linkerd/linkerd2/controller/gen/client/listers/policy/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
cache "k8s.io/client-go/tools/cache"
)
// MeshTLSAuthenticationInformer provides access to a shared informer and lister for
// MeshTLSAuthentications.
type MeshTLSAuthenticationInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1alpha1.MeshTLSAuthenticationLister
}
type meshTLSAuthenticationInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
namespace string
}
// NewMeshTLSAuthenticationInformer constructs a new informer for MeshTLSAuthentication type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewMeshTLSAuthenticationInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredMeshTLSAuthenticationInformer(client, namespace, resyncPeriod, indexers, nil)
}
// NewFilteredMeshTLSAuthenticationInformer constructs a new informer for MeshTLSAuthentication type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewFilteredMeshTLSAuthenticationInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.PolicyV1alpha1().MeshTLSAuthentications(namespace).List(context.TODO(), options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.PolicyV1alpha1().MeshTLSAuthentications(namespace).Watch(context.TODO(), options)
},
},
&policyv1alpha1.MeshTLSAuthentication{},
resyncPeriod,
indexers,
)
}
func (f *meshTLSAuthenticationInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredMeshTLSAuthenticationInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *meshTLSAuthenticationInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&policyv1alpha1.MeshTLSAuthentication{}, f.defaultInformer)
}
func (f *meshTLSAuthenticationInformer) Lister() v1alpha1.MeshTLSAuthenticationLister {
return v1alpha1.NewMeshTLSAuthenticationLister(f.Informer().GetIndexer())
}
/*
Copyright 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.
*/
// Code generated by informer-gen. DO NOT EDIT.
package v1alpha1
import (
"context"
time "time"
policyv1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1"
versioned "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned"
internalinterfaces "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces"
v1alpha1 "github.com/linkerd/linkerd2/controller/gen/client/listers/policy/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
cache "k8s.io/client-go/tools/cache"
)
// NetworkAuthenticationInformer provides access to a shared informer and lister for
// NetworkAuthentications.
type NetworkAuthenticationInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1alpha1.NetworkAuthenticationLister
}
type networkAuthenticationInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
namespace string
}
// NewNetworkAuthenticationInformer constructs a new informer for NetworkAuthentication type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewNetworkAuthenticationInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredNetworkAuthenticationInformer(client, namespace, resyncPeriod, indexers, nil)
}
// NewFilteredNetworkAuthenticationInformer constructs a new informer for NetworkAuthentication type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewFilteredNetworkAuthenticationInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.PolicyV1alpha1().NetworkAuthentications(namespace).List(context.TODO(), options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.PolicyV1alpha1().NetworkAuthentications(namespace).Watch(context.TODO(), options)
},
},
&policyv1alpha1.NetworkAuthentication{},
resyncPeriod,
indexers,
)
}
func (f *networkAuthenticationInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredNetworkAuthenticationInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *networkAuthenticationInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&policyv1alpha1.NetworkAuthentication{}, f.defaultInformer)
}
func (f *networkAuthenticationInformer) Lister() v1alpha1.NetworkAuthenticationLister {
return v1alpha1.NewNetworkAuthenticationLister(f.Informer().GetIndexer())
}
/*
Copyright 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.
*/
// Code generated by informer-gen. DO NOT EDIT.
package v1beta3
import (
"context"
time "time"
policyv1beta3 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1beta3"
versioned "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned"
internalinterfaces "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces"
v1beta3 "github.com/linkerd/linkerd2/controller/gen/client/listers/policy/v1beta3"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
cache "k8s.io/client-go/tools/cache"
)
// HTTPRouteInformer provides access to a shared informer and lister for
// HTTPRoutes.
type HTTPRouteInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1beta3.HTTPRouteLister
}
type hTTPRouteInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
namespace string
}
// NewHTTPRouteInformer constructs a new informer for HTTPRoute type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewHTTPRouteInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredHTTPRouteInformer(client, namespace, resyncPeriod, indexers, nil)
}
// NewFilteredHTTPRouteInformer constructs a new informer for HTTPRoute type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewFilteredHTTPRouteInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.PolicyV1beta3().HTTPRoutes(namespace).List(context.TODO(), options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.PolicyV1beta3().HTTPRoutes(namespace).Watch(context.TODO(), options)
},
},
&policyv1beta3.HTTPRoute{},
resyncPeriod,
indexers,
)
}
func (f *hTTPRouteInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredHTTPRouteInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *hTTPRouteInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&policyv1beta3.HTTPRoute{}, f.defaultInformer)
}
func (f *hTTPRouteInformer) Lister() v1beta3.HTTPRouteLister {
return v1beta3.NewHTTPRouteLister(f.Informer().GetIndexer())
}
/*
Copyright 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.
*/
// Code generated by informer-gen. DO NOT EDIT.
package v1beta3
import (
internalinterfaces "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces"
)
// Interface provides access to all the informers in this group version.
type Interface interface {
// HTTPRoutes returns a HTTPRouteInformer.
HTTPRoutes() HTTPRouteInformer
}
type version struct {
factory internalinterfaces.SharedInformerFactory
namespace string
tweakListOptions internalinterfaces.TweakListOptionsFunc
}
// New returns a new Interface.
func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// HTTPRoutes returns a HTTPRouteInformer.
func (v *version) HTTPRoutes() HTTPRouteInformer {
return &hTTPRouteInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
/*
Copyright 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.
*/
// Code generated by informer-gen. DO NOT EDIT.
package server
import (
internalinterfaces "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces"
v1beta1 "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/server/v1beta1"
v1beta2 "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/server/v1beta2"
)
// Interface provides access to each of this group's versions.
type Interface interface {
// V1beta1 provides access to shared informers for resources in V1beta1.
V1beta1() v1beta1.Interface
// V1beta2 provides access to shared informers for resources in V1beta2.
V1beta2() v1beta2.Interface
}
type group struct {
factory internalinterfaces.SharedInformerFactory
namespace string
tweakListOptions internalinterfaces.TweakListOptionsFunc
}
// New returns a new Interface.
func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// V1beta1 returns a new v1beta1.Interface.
func (g *group) V1beta1() v1beta1.Interface {
return v1beta1.New(g.factory, g.namespace, g.tweakListOptions)
}
// V1beta2 returns a new v1beta2.Interface.
func (g *group) V1beta2() v1beta2.Interface {
return v1beta2.New(g.factory, g.namespace, g.tweakListOptions)
}
/*
Copyright 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.
*/
// Code generated by informer-gen. DO NOT EDIT.
package v1beta1
import (
internalinterfaces "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces"
)
// Interface provides access to all the informers in this group version.
type Interface interface {
// Servers returns a ServerInformer.
Servers() ServerInformer
}
type version struct {
factory internalinterfaces.SharedInformerFactory
namespace string
tweakListOptions internalinterfaces.TweakListOptionsFunc
}
// New returns a new Interface.
func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// Servers returns a ServerInformer.
func (v *version) Servers() ServerInformer {
return &serverInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
/*
Copyright 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.
*/
// Code generated by informer-gen. DO NOT EDIT.
package v1beta1
import (
"context"
time "time"
serverv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta1"
versioned "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned"
internalinterfaces "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces"
v1beta1 "github.com/linkerd/linkerd2/controller/gen/client/listers/server/v1beta1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
cache "k8s.io/client-go/tools/cache"
)
// ServerInformer provides access to a shared informer and lister for
// Servers.
type ServerInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1beta1.ServerLister
}
type serverInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
namespace string
}
// NewServerInformer constructs a new informer for Server type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewServerInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredServerInformer(client, namespace, resyncPeriod, indexers, nil)
}
// NewFilteredServerInformer constructs a new informer for Server type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewFilteredServerInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.ServerV1beta1().Servers(namespace).List(context.TODO(), options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.ServerV1beta1().Servers(namespace).Watch(context.TODO(), options)
},
},
&serverv1beta1.Server{},
resyncPeriod,
indexers,
)
}
func (f *serverInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredServerInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *serverInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&serverv1beta1.Server{}, f.defaultInformer)
}
func (f *serverInformer) Lister() v1beta1.ServerLister {
return v1beta1.NewServerLister(f.Informer().GetIndexer())
}
/*
Copyright 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.
*/
// Code generated by informer-gen. DO NOT EDIT.
package v1beta2
import (
internalinterfaces "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces"
)
// Interface provides access to all the informers in this group version.
type Interface interface {
// Servers returns a ServerInformer.
Servers() ServerInformer
}
type version struct {
factory internalinterfaces.SharedInformerFactory
namespace string
tweakListOptions internalinterfaces.TweakListOptionsFunc
}
// New returns a new Interface.
func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// Servers returns a ServerInformer.
func (v *version) Servers() ServerInformer {
return &serverInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
/*
Copyright 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.
*/
// Code generated by informer-gen. DO NOT EDIT.
package v1beta2
import (
"context"
time "time"
serverv1beta2 "github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta2"
versioned "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned"
internalinterfaces "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces"
v1beta2 "github.com/linkerd/linkerd2/controller/gen/client/listers/server/v1beta2"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
cache "k8s.io/client-go/tools/cache"
)
// ServerInformer provides access to a shared informer and lister for
// Servers.
type ServerInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1beta2.ServerLister
}
type serverInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
namespace string
}
// NewServerInformer constructs a new informer for Server type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewServerInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredServerInformer(client, namespace, resyncPeriod, indexers, nil)
}
// NewFilteredServerInformer constructs a new informer for Server type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewFilteredServerInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.ServerV1beta2().Servers(namespace).List(context.TODO(), options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.ServerV1beta2().Servers(namespace).Watch(context.TODO(), options)
},
},
&serverv1beta2.Server{},
resyncPeriod,
indexers,
)
}
func (f *serverInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredServerInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *serverInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&serverv1beta2.Server{}, f.defaultInformer)
}
func (f *serverInformer) Lister() v1beta2.ServerLister {
return v1beta2.NewServerLister(f.Informer().GetIndexer())
}
/*
Copyright 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.
*/
// Code generated by informer-gen. DO NOT EDIT.
package serverauthorization
import (
internalinterfaces "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces"
v1beta1 "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/serverauthorization/v1beta1"
)
// Interface provides access to each of this group's versions.
type Interface interface {
// V1beta1 provides access to shared informers for resources in V1beta1.
V1beta1() v1beta1.Interface
}
type group struct {
factory internalinterfaces.SharedInformerFactory
namespace string
tweakListOptions internalinterfaces.TweakListOptionsFunc
}
// New returns a new Interface.
func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// V1beta1 returns a new v1beta1.Interface.
func (g *group) V1beta1() v1beta1.Interface {
return v1beta1.New(g.factory, g.namespace, g.tweakListOptions)
}
/*
Copyright 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.
*/
// Code generated by informer-gen. DO NOT EDIT.
package v1beta1
import (
internalinterfaces "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces"
)
// Interface provides access to all the informers in this group version.
type Interface interface {
// ServerAuthorizations returns a ServerAuthorizationInformer.
ServerAuthorizations() ServerAuthorizationInformer
}
type version struct {
factory internalinterfaces.SharedInformerFactory
namespace string
tweakListOptions internalinterfaces.TweakListOptionsFunc
}
// New returns a new Interface.
func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// ServerAuthorizations returns a ServerAuthorizationInformer.
func (v *version) ServerAuthorizations() ServerAuthorizationInformer {
return &serverAuthorizationInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
/*
Copyright 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.
*/
// Code generated by informer-gen. DO NOT EDIT.
package v1beta1
import (
"context"
time "time"
serverauthorizationv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/serverauthorization/v1beta1"
versioned "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned"
internalinterfaces "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces"
v1beta1 "github.com/linkerd/linkerd2/controller/gen/client/listers/serverauthorization/v1beta1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
cache "k8s.io/client-go/tools/cache"
)
// ServerAuthorizationInformer provides access to a shared informer and lister for
// ServerAuthorizations.
type ServerAuthorizationInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1beta1.ServerAuthorizationLister
}
type serverAuthorizationInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
namespace string
}
// NewServerAuthorizationInformer constructs a new informer for ServerAuthorization type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewServerAuthorizationInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredServerAuthorizationInformer(client, namespace, resyncPeriod, indexers, nil)
}
// NewFilteredServerAuthorizationInformer constructs a new informer for ServerAuthorization type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewFilteredServerAuthorizationInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.ServerauthorizationV1beta1().ServerAuthorizations(namespace).List(context.TODO(), options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.ServerauthorizationV1beta1().ServerAuthorizations(namespace).Watch(context.TODO(), options)
},
},
&serverauthorizationv1beta1.ServerAuthorization{},
resyncPeriod,
indexers,
)
}
func (f *serverAuthorizationInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredServerAuthorizationInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *serverAuthorizationInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&serverauthorizationv1beta1.ServerAuthorization{}, f.defaultInformer)
}
func (f *serverAuthorizationInformer) Lister() v1beta1.ServerAuthorizationLister {
return v1beta1.NewServerAuthorizationLister(f.Informer().GetIndexer())
}
/*
Copyright 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.
*/
// Code generated by informer-gen. DO NOT EDIT.
package serviceprofile
import (
internalinterfaces "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces"
v1alpha2 "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/serviceprofile/v1alpha2"
)
// Interface provides access to each of this group's versions.
type Interface interface {
// V1alpha2 provides access to shared informers for resources in V1alpha2.
V1alpha2() v1alpha2.Interface
}
type group struct {
factory internalinterfaces.SharedInformerFactory
namespace string
tweakListOptions internalinterfaces.TweakListOptionsFunc
}
// New returns a new Interface.
func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// V1alpha2 returns a new v1alpha2.Interface.
func (g *group) V1alpha2() v1alpha2.Interface {
return v1alpha2.New(g.factory, g.namespace, g.tweakListOptions)
}
/*
Copyright 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.
*/
// Code generated by informer-gen. DO NOT EDIT.
package v1alpha2
import (
internalinterfaces "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces"
)
// Interface provides access to all the informers in this group version.
type Interface interface {
// ServiceProfiles returns a ServiceProfileInformer.
ServiceProfiles() ServiceProfileInformer
}
type version struct {
factory internalinterfaces.SharedInformerFactory
namespace string
tweakListOptions internalinterfaces.TweakListOptionsFunc
}
// New returns a new Interface.
func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// ServiceProfiles returns a ServiceProfileInformer.
func (v *version) ServiceProfiles() ServiceProfileInformer {
return &serviceProfileInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
/*
Copyright 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.
*/
// Code generated by informer-gen. DO NOT EDIT.
package v1alpha2
import (
"context"
time "time"
serviceprofilev1alpha2 "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
versioned "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned"
internalinterfaces "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces"
v1alpha2 "github.com/linkerd/linkerd2/controller/gen/client/listers/serviceprofile/v1alpha2"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
cache "k8s.io/client-go/tools/cache"
)
// ServiceProfileInformer provides access to a shared informer and lister for
// ServiceProfiles.
type ServiceProfileInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1alpha2.ServiceProfileLister
}
type serviceProfileInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
namespace string
}
// NewServiceProfileInformer constructs a new informer for ServiceProfile type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewServiceProfileInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredServiceProfileInformer(client, namespace, resyncPeriod, indexers, nil)
}
// NewFilteredServiceProfileInformer constructs a new informer for ServiceProfile type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewFilteredServiceProfileInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.LinkerdV1alpha2().ServiceProfiles(namespace).List(context.TODO(), options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.LinkerdV1alpha2().ServiceProfiles(namespace).Watch(context.TODO(), options)
},
},
&serviceprofilev1alpha2.ServiceProfile{},
resyncPeriod,
indexers,
)
}
func (f *serviceProfileInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredServiceProfileInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *serviceProfileInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&serviceprofilev1alpha2.ServiceProfile{}, f.defaultInformer)
}
func (f *serviceProfileInformer) Lister() v1alpha2.ServiceProfileLister {
return v1alpha2.NewServiceProfileLister(f.Informer().GetIndexer())
}
/*
Copyright 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.
*/
// Code generated by lister-gen. DO NOT EDIT.
package v1beta1
import (
v1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
)
// ExternalWorkloadLister helps list ExternalWorkloads.
// All objects returned here must be treated as read-only.
type ExternalWorkloadLister interface {
// List lists all ExternalWorkloads in the indexer.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1beta1.ExternalWorkload, err error)
// ExternalWorkloads returns an object that can list and get ExternalWorkloads.
ExternalWorkloads(namespace string) ExternalWorkloadNamespaceLister
ExternalWorkloadListerExpansion
}
// externalWorkloadLister implements the ExternalWorkloadLister interface.
type externalWorkloadLister struct {
indexer cache.Indexer
}
// NewExternalWorkloadLister returns a new ExternalWorkloadLister.
func NewExternalWorkloadLister(indexer cache.Indexer) ExternalWorkloadLister {
return &externalWorkloadLister{indexer: indexer}
}
// List lists all ExternalWorkloads in the indexer.
func (s *externalWorkloadLister) List(selector labels.Selector) (ret []*v1beta1.ExternalWorkload, err error) {
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*v1beta1.ExternalWorkload))
})
return ret, err
}
// ExternalWorkloads returns an object that can list and get ExternalWorkloads.
func (s *externalWorkloadLister) ExternalWorkloads(namespace string) ExternalWorkloadNamespaceLister {
return externalWorkloadNamespaceLister{indexer: s.indexer, namespace: namespace}
}
// ExternalWorkloadNamespaceLister helps list and get ExternalWorkloads.
// All objects returned here must be treated as read-only.
type ExternalWorkloadNamespaceLister interface {
// List lists all ExternalWorkloads in the indexer for a given namespace.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1beta1.ExternalWorkload, err error)
// Get retrieves the ExternalWorkload from the indexer for a given namespace and name.
// Objects returned here must be treated as read-only.
Get(name string) (*v1beta1.ExternalWorkload, error)
ExternalWorkloadNamespaceListerExpansion
}
// externalWorkloadNamespaceLister implements the ExternalWorkloadNamespaceLister
// interface.
type externalWorkloadNamespaceLister struct {
indexer cache.Indexer
namespace string
}
// List lists all ExternalWorkloads in the indexer for a given namespace.
func (s externalWorkloadNamespaceLister) List(selector labels.Selector) (ret []*v1beta1.ExternalWorkload, err error) {
err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
ret = append(ret, m.(*v1beta1.ExternalWorkload))
})
return ret, err
}
// Get retrieves the ExternalWorkload from the indexer for a given namespace and name.
func (s externalWorkloadNamespaceLister) Get(name string) (*v1beta1.ExternalWorkload, error) {
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(v1beta1.Resource("externalworkload"), name)
}
return obj.(*v1beta1.ExternalWorkload), nil
}
/*
Copyright 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.
*/
// Code generated by lister-gen. DO NOT EDIT.
package v1alpha1
import (
v1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
)
// LinkLister helps list Links.
// All objects returned here must be treated as read-only.
type LinkLister interface {
// List lists all Links in the indexer.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1alpha1.Link, err error)
// Links returns an object that can list and get Links.
Links(namespace string) LinkNamespaceLister
LinkListerExpansion
}
// linkLister implements the LinkLister interface.
type linkLister struct {
indexer cache.Indexer
}
// NewLinkLister returns a new LinkLister.
func NewLinkLister(indexer cache.Indexer) LinkLister {
return &linkLister{indexer: indexer}
}
// List lists all Links in the indexer.
func (s *linkLister) List(selector labels.Selector) (ret []*v1alpha1.Link, err error) {
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha1.Link))
})
return ret, err
}
// Links returns an object that can list and get Links.
func (s *linkLister) Links(namespace string) LinkNamespaceLister {
return linkNamespaceLister{indexer: s.indexer, namespace: namespace}
}
// LinkNamespaceLister helps list and get Links.
// All objects returned here must be treated as read-only.
type LinkNamespaceLister interface {
// List lists all Links in the indexer for a given namespace.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1alpha1.Link, err error)
// Get retrieves the Link from the indexer for a given namespace and name.
// Objects returned here must be treated as read-only.
Get(name string) (*v1alpha1.Link, error)
LinkNamespaceListerExpansion
}
// linkNamespaceLister implements the LinkNamespaceLister
// interface.
type linkNamespaceLister struct {
indexer cache.Indexer
namespace string
}
// List lists all Links in the indexer for a given namespace.
func (s linkNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.Link, err error) {
err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha1.Link))
})
return ret, err
}
// Get retrieves the Link from the indexer for a given namespace and name.
func (s linkNamespaceLister) Get(name string) (*v1alpha1.Link, error) {
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(v1alpha1.Resource("link"), name)
}
return obj.(*v1alpha1.Link), nil
}
/*
Copyright 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.
*/
// Code generated by lister-gen. DO NOT EDIT.
package v1alpha1
import (
v1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
)
// AuthorizationPolicyLister helps list AuthorizationPolicies.
// All objects returned here must be treated as read-only.
type AuthorizationPolicyLister interface {
// List lists all AuthorizationPolicies in the indexer.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1alpha1.AuthorizationPolicy, err error)
// AuthorizationPolicies returns an object that can list and get AuthorizationPolicies.
AuthorizationPolicies(namespace string) AuthorizationPolicyNamespaceLister
AuthorizationPolicyListerExpansion
}
// authorizationPolicyLister implements the AuthorizationPolicyLister interface.
type authorizationPolicyLister struct {
indexer cache.Indexer
}
// NewAuthorizationPolicyLister returns a new AuthorizationPolicyLister.
func NewAuthorizationPolicyLister(indexer cache.Indexer) AuthorizationPolicyLister {
return &authorizationPolicyLister{indexer: indexer}
}
// List lists all AuthorizationPolicies in the indexer.
func (s *authorizationPolicyLister) List(selector labels.Selector) (ret []*v1alpha1.AuthorizationPolicy, err error) {
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha1.AuthorizationPolicy))
})
return ret, err
}
// AuthorizationPolicies returns an object that can list and get AuthorizationPolicies.
func (s *authorizationPolicyLister) AuthorizationPolicies(namespace string) AuthorizationPolicyNamespaceLister {
return authorizationPolicyNamespaceLister{indexer: s.indexer, namespace: namespace}
}
// AuthorizationPolicyNamespaceLister helps list and get AuthorizationPolicies.
// All objects returned here must be treated as read-only.
type AuthorizationPolicyNamespaceLister interface {
// List lists all AuthorizationPolicies in the indexer for a given namespace.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1alpha1.AuthorizationPolicy, err error)
// Get retrieves the AuthorizationPolicy from the indexer for a given namespace and name.
// Objects returned here must be treated as read-only.
Get(name string) (*v1alpha1.AuthorizationPolicy, error)
AuthorizationPolicyNamespaceListerExpansion
}
// authorizationPolicyNamespaceLister implements the AuthorizationPolicyNamespaceLister
// interface.
type authorizationPolicyNamespaceLister struct {
indexer cache.Indexer
namespace string
}
// List lists all AuthorizationPolicies in the indexer for a given namespace.
func (s authorizationPolicyNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.AuthorizationPolicy, err error) {
err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha1.AuthorizationPolicy))
})
return ret, err
}
// Get retrieves the AuthorizationPolicy from the indexer for a given namespace and name.
func (s authorizationPolicyNamespaceLister) Get(name string) (*v1alpha1.AuthorizationPolicy, error) {
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(v1alpha1.Resource("authorizationpolicy"), name)
}
return obj.(*v1alpha1.AuthorizationPolicy), nil
}
/*
Copyright 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.
*/
// Code generated by lister-gen. DO NOT EDIT.
package v1alpha1
import (
v1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
)
// HTTPRouteLister helps list HTTPRoutes.
// All objects returned here must be treated as read-only.
type HTTPRouteLister interface {
// List lists all HTTPRoutes in the indexer.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1alpha1.HTTPRoute, err error)
// HTTPRoutes returns an object that can list and get HTTPRoutes.
HTTPRoutes(namespace string) HTTPRouteNamespaceLister
HTTPRouteListerExpansion
}
// hTTPRouteLister implements the HTTPRouteLister interface.
type hTTPRouteLister struct {
indexer cache.Indexer
}
// NewHTTPRouteLister returns a new HTTPRouteLister.
func NewHTTPRouteLister(indexer cache.Indexer) HTTPRouteLister {
return &hTTPRouteLister{indexer: indexer}
}
// List lists all HTTPRoutes in the indexer.
func (s *hTTPRouteLister) List(selector labels.Selector) (ret []*v1alpha1.HTTPRoute, err error) {
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha1.HTTPRoute))
})
return ret, err
}
// HTTPRoutes returns an object that can list and get HTTPRoutes.
func (s *hTTPRouteLister) HTTPRoutes(namespace string) HTTPRouteNamespaceLister {
return hTTPRouteNamespaceLister{indexer: s.indexer, namespace: namespace}
}
// HTTPRouteNamespaceLister helps list and get HTTPRoutes.
// All objects returned here must be treated as read-only.
type HTTPRouteNamespaceLister interface {
// List lists all HTTPRoutes in the indexer for a given namespace.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1alpha1.HTTPRoute, err error)
// Get retrieves the HTTPRoute from the indexer for a given namespace and name.
// Objects returned here must be treated as read-only.
Get(name string) (*v1alpha1.HTTPRoute, error)
HTTPRouteNamespaceListerExpansion
}
// hTTPRouteNamespaceLister implements the HTTPRouteNamespaceLister
// interface.
type hTTPRouteNamespaceLister struct {
indexer cache.Indexer
namespace string
}
// List lists all HTTPRoutes in the indexer for a given namespace.
func (s hTTPRouteNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.HTTPRoute, err error) {
err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha1.HTTPRoute))
})
return ret, err
}
// Get retrieves the HTTPRoute from the indexer for a given namespace and name.
func (s hTTPRouteNamespaceLister) Get(name string) (*v1alpha1.HTTPRoute, error) {
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(v1alpha1.Resource("httproute"), name)
}
return obj.(*v1alpha1.HTTPRoute), nil
}
/*
Copyright 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.
*/
// Code generated by lister-gen. DO NOT EDIT.
package v1alpha1
import (
v1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
)
// MeshTLSAuthenticationLister helps list MeshTLSAuthentications.
// All objects returned here must be treated as read-only.
type MeshTLSAuthenticationLister interface {
// List lists all MeshTLSAuthentications in the indexer.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1alpha1.MeshTLSAuthentication, err error)
// MeshTLSAuthentications returns an object that can list and get MeshTLSAuthentications.
MeshTLSAuthentications(namespace string) MeshTLSAuthenticationNamespaceLister
MeshTLSAuthenticationListerExpansion
}
// meshTLSAuthenticationLister implements the MeshTLSAuthenticationLister interface.
type meshTLSAuthenticationLister struct {
indexer cache.Indexer
}
// NewMeshTLSAuthenticationLister returns a new MeshTLSAuthenticationLister.
func NewMeshTLSAuthenticationLister(indexer cache.Indexer) MeshTLSAuthenticationLister {
return &meshTLSAuthenticationLister{indexer: indexer}
}
// List lists all MeshTLSAuthentications in the indexer.
func (s *meshTLSAuthenticationLister) List(selector labels.Selector) (ret []*v1alpha1.MeshTLSAuthentication, err error) {
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha1.MeshTLSAuthentication))
})
return ret, err
}
// MeshTLSAuthentications returns an object that can list and get MeshTLSAuthentications.
func (s *meshTLSAuthenticationLister) MeshTLSAuthentications(namespace string) MeshTLSAuthenticationNamespaceLister {
return meshTLSAuthenticationNamespaceLister{indexer: s.indexer, namespace: namespace}
}
// MeshTLSAuthenticationNamespaceLister helps list and get MeshTLSAuthentications.
// All objects returned here must be treated as read-only.
type MeshTLSAuthenticationNamespaceLister interface {
// List lists all MeshTLSAuthentications in the indexer for a given namespace.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1alpha1.MeshTLSAuthentication, err error)
// Get retrieves the MeshTLSAuthentication from the indexer for a given namespace and name.
// Objects returned here must be treated as read-only.
Get(name string) (*v1alpha1.MeshTLSAuthentication, error)
MeshTLSAuthenticationNamespaceListerExpansion
}
// meshTLSAuthenticationNamespaceLister implements the MeshTLSAuthenticationNamespaceLister
// interface.
type meshTLSAuthenticationNamespaceLister struct {
indexer cache.Indexer
namespace string
}
// List lists all MeshTLSAuthentications in the indexer for a given namespace.
func (s meshTLSAuthenticationNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.MeshTLSAuthentication, err error) {
err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha1.MeshTLSAuthentication))
})
return ret, err
}
// Get retrieves the MeshTLSAuthentication from the indexer for a given namespace and name.
func (s meshTLSAuthenticationNamespaceLister) Get(name string) (*v1alpha1.MeshTLSAuthentication, error) {
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(v1alpha1.Resource("meshtlsauthentication"), name)
}
return obj.(*v1alpha1.MeshTLSAuthentication), nil
}
/*
Copyright 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.
*/
// Code generated by lister-gen. DO NOT EDIT.
package v1alpha1
import (
v1alpha1 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
)
// NetworkAuthenticationLister helps list NetworkAuthentications.
// All objects returned here must be treated as read-only.
type NetworkAuthenticationLister interface {
// List lists all NetworkAuthentications in the indexer.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1alpha1.NetworkAuthentication, err error)
// NetworkAuthentications returns an object that can list and get NetworkAuthentications.
NetworkAuthentications(namespace string) NetworkAuthenticationNamespaceLister
NetworkAuthenticationListerExpansion
}
// networkAuthenticationLister implements the NetworkAuthenticationLister interface.
type networkAuthenticationLister struct {
indexer cache.Indexer
}
// NewNetworkAuthenticationLister returns a new NetworkAuthenticationLister.
func NewNetworkAuthenticationLister(indexer cache.Indexer) NetworkAuthenticationLister {
return &networkAuthenticationLister{indexer: indexer}
}
// List lists all NetworkAuthentications in the indexer.
func (s *networkAuthenticationLister) List(selector labels.Selector) (ret []*v1alpha1.NetworkAuthentication, err error) {
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha1.NetworkAuthentication))
})
return ret, err
}
// NetworkAuthentications returns an object that can list and get NetworkAuthentications.
func (s *networkAuthenticationLister) NetworkAuthentications(namespace string) NetworkAuthenticationNamespaceLister {
return networkAuthenticationNamespaceLister{indexer: s.indexer, namespace: namespace}
}
// NetworkAuthenticationNamespaceLister helps list and get NetworkAuthentications.
// All objects returned here must be treated as read-only.
type NetworkAuthenticationNamespaceLister interface {
// List lists all NetworkAuthentications in the indexer for a given namespace.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1alpha1.NetworkAuthentication, err error)
// Get retrieves the NetworkAuthentication from the indexer for a given namespace and name.
// Objects returned here must be treated as read-only.
Get(name string) (*v1alpha1.NetworkAuthentication, error)
NetworkAuthenticationNamespaceListerExpansion
}
// networkAuthenticationNamespaceLister implements the NetworkAuthenticationNamespaceLister
// interface.
type networkAuthenticationNamespaceLister struct {
indexer cache.Indexer
namespace string
}
// List lists all NetworkAuthentications in the indexer for a given namespace.
func (s networkAuthenticationNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.NetworkAuthentication, err error) {
err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha1.NetworkAuthentication))
})
return ret, err
}
// Get retrieves the NetworkAuthentication from the indexer for a given namespace and name.
func (s networkAuthenticationNamespaceLister) Get(name string) (*v1alpha1.NetworkAuthentication, error) {
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(v1alpha1.Resource("networkauthentication"), name)
}
return obj.(*v1alpha1.NetworkAuthentication), nil
}
/*
Copyright 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.
*/
// Code generated by lister-gen. DO NOT EDIT.
package v1beta3
import (
v1beta3 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1beta3"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
)
// HTTPRouteLister helps list HTTPRoutes.
// All objects returned here must be treated as read-only.
type HTTPRouteLister interface {
// List lists all HTTPRoutes in the indexer.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1beta3.HTTPRoute, err error)
// HTTPRoutes returns an object that can list and get HTTPRoutes.
HTTPRoutes(namespace string) HTTPRouteNamespaceLister
HTTPRouteListerExpansion
}
// hTTPRouteLister implements the HTTPRouteLister interface.
type hTTPRouteLister struct {
indexer cache.Indexer
}
// NewHTTPRouteLister returns a new HTTPRouteLister.
func NewHTTPRouteLister(indexer cache.Indexer) HTTPRouteLister {
return &hTTPRouteLister{indexer: indexer}
}
// List lists all HTTPRoutes in the indexer.
func (s *hTTPRouteLister) List(selector labels.Selector) (ret []*v1beta3.HTTPRoute, err error) {
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*v1beta3.HTTPRoute))
})
return ret, err
}
// HTTPRoutes returns an object that can list and get HTTPRoutes.
func (s *hTTPRouteLister) HTTPRoutes(namespace string) HTTPRouteNamespaceLister {
return hTTPRouteNamespaceLister{indexer: s.indexer, namespace: namespace}
}
// HTTPRouteNamespaceLister helps list and get HTTPRoutes.
// All objects returned here must be treated as read-only.
type HTTPRouteNamespaceLister interface {
// List lists all HTTPRoutes in the indexer for a given namespace.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1beta3.HTTPRoute, err error)
// Get retrieves the HTTPRoute from the indexer for a given namespace and name.
// Objects returned here must be treated as read-only.
Get(name string) (*v1beta3.HTTPRoute, error)
HTTPRouteNamespaceListerExpansion
}
// hTTPRouteNamespaceLister implements the HTTPRouteNamespaceLister
// interface.
type hTTPRouteNamespaceLister struct {
indexer cache.Indexer
namespace string
}
// List lists all HTTPRoutes in the indexer for a given namespace.
func (s hTTPRouteNamespaceLister) List(selector labels.Selector) (ret []*v1beta3.HTTPRoute, err error) {
err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
ret = append(ret, m.(*v1beta3.HTTPRoute))
})
return ret, err
}
// Get retrieves the HTTPRoute from the indexer for a given namespace and name.
func (s hTTPRouteNamespaceLister) Get(name string) (*v1beta3.HTTPRoute, error) {
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(v1beta3.Resource("httproute"), name)
}
return obj.(*v1beta3.HTTPRoute), nil
}
/*
Copyright 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.
*/
// Code generated by lister-gen. DO NOT EDIT.
package v1beta1
import (
v1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
)
// ServerLister helps list Servers.
// All objects returned here must be treated as read-only.
type ServerLister interface {
// List lists all Servers in the indexer.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1beta1.Server, err error)
// Servers returns an object that can list and get Servers.
Servers(namespace string) ServerNamespaceLister
ServerListerExpansion
}
// serverLister implements the ServerLister interface.
type serverLister struct {
indexer cache.Indexer
}
// NewServerLister returns a new ServerLister.
func NewServerLister(indexer cache.Indexer) ServerLister {
return &serverLister{indexer: indexer}
}
// List lists all Servers in the indexer.
func (s *serverLister) List(selector labels.Selector) (ret []*v1beta1.Server, err error) {
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*v1beta1.Server))
})
return ret, err
}
// Servers returns an object that can list and get Servers.
func (s *serverLister) Servers(namespace string) ServerNamespaceLister {
return serverNamespaceLister{indexer: s.indexer, namespace: namespace}
}
// ServerNamespaceLister helps list and get Servers.
// All objects returned here must be treated as read-only.
type ServerNamespaceLister interface {
// List lists all Servers in the indexer for a given namespace.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1beta1.Server, err error)
// Get retrieves the Server from the indexer for a given namespace and name.
// Objects returned here must be treated as read-only.
Get(name string) (*v1beta1.Server, error)
ServerNamespaceListerExpansion
}
// serverNamespaceLister implements the ServerNamespaceLister
// interface.
type serverNamespaceLister struct {
indexer cache.Indexer
namespace string
}
// List lists all Servers in the indexer for a given namespace.
func (s serverNamespaceLister) List(selector labels.Selector) (ret []*v1beta1.Server, err error) {
err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
ret = append(ret, m.(*v1beta1.Server))
})
return ret, err
}
// Get retrieves the Server from the indexer for a given namespace and name.
func (s serverNamespaceLister) Get(name string) (*v1beta1.Server, error) {
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(v1beta1.Resource("server"), name)
}
return obj.(*v1beta1.Server), nil
}
/*
Copyright 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.
*/
// Code generated by lister-gen. DO NOT EDIT.
package v1beta2
import (
v1beta2 "github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta2"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
)
// ServerLister helps list Servers.
// All objects returned here must be treated as read-only.
type ServerLister interface {
// List lists all Servers in the indexer.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1beta2.Server, err error)
// Servers returns an object that can list and get Servers.
Servers(namespace string) ServerNamespaceLister
ServerListerExpansion
}
// serverLister implements the ServerLister interface.
type serverLister struct {
indexer cache.Indexer
}
// NewServerLister returns a new ServerLister.
func NewServerLister(indexer cache.Indexer) ServerLister {
return &serverLister{indexer: indexer}
}
// List lists all Servers in the indexer.
func (s *serverLister) List(selector labels.Selector) (ret []*v1beta2.Server, err error) {
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*v1beta2.Server))
})
return ret, err
}
// Servers returns an object that can list and get Servers.
func (s *serverLister) Servers(namespace string) ServerNamespaceLister {
return serverNamespaceLister{indexer: s.indexer, namespace: namespace}
}
// ServerNamespaceLister helps list and get Servers.
// All objects returned here must be treated as read-only.
type ServerNamespaceLister interface {
// List lists all Servers in the indexer for a given namespace.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1beta2.Server, err error)
// Get retrieves the Server from the indexer for a given namespace and name.
// Objects returned here must be treated as read-only.
Get(name string) (*v1beta2.Server, error)
ServerNamespaceListerExpansion
}
// serverNamespaceLister implements the ServerNamespaceLister
// interface.
type serverNamespaceLister struct {
indexer cache.Indexer
namespace string
}
// List lists all Servers in the indexer for a given namespace.
func (s serverNamespaceLister) List(selector labels.Selector) (ret []*v1beta2.Server, err error) {
err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
ret = append(ret, m.(*v1beta2.Server))
})
return ret, err
}
// Get retrieves the Server from the indexer for a given namespace and name.
func (s serverNamespaceLister) Get(name string) (*v1beta2.Server, error) {
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(v1beta2.Resource("server"), name)
}
return obj.(*v1beta2.Server), nil
}
/*
Copyright 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.
*/
// Code generated by lister-gen. DO NOT EDIT.
package v1beta1
import (
v1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/serverauthorization/v1beta1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
)
// ServerAuthorizationLister helps list ServerAuthorizations.
// All objects returned here must be treated as read-only.
type ServerAuthorizationLister interface {
// List lists all ServerAuthorizations in the indexer.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1beta1.ServerAuthorization, err error)
// ServerAuthorizations returns an object that can list and get ServerAuthorizations.
ServerAuthorizations(namespace string) ServerAuthorizationNamespaceLister
ServerAuthorizationListerExpansion
}
// serverAuthorizationLister implements the ServerAuthorizationLister interface.
type serverAuthorizationLister struct {
indexer cache.Indexer
}
// NewServerAuthorizationLister returns a new ServerAuthorizationLister.
func NewServerAuthorizationLister(indexer cache.Indexer) ServerAuthorizationLister {
return &serverAuthorizationLister{indexer: indexer}
}
// List lists all ServerAuthorizations in the indexer.
func (s *serverAuthorizationLister) List(selector labels.Selector) (ret []*v1beta1.ServerAuthorization, err error) {
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*v1beta1.ServerAuthorization))
})
return ret, err
}
// ServerAuthorizations returns an object that can list and get ServerAuthorizations.
func (s *serverAuthorizationLister) ServerAuthorizations(namespace string) ServerAuthorizationNamespaceLister {
return serverAuthorizationNamespaceLister{indexer: s.indexer, namespace: namespace}
}
// ServerAuthorizationNamespaceLister helps list and get ServerAuthorizations.
// All objects returned here must be treated as read-only.
type ServerAuthorizationNamespaceLister interface {
// List lists all ServerAuthorizations in the indexer for a given namespace.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1beta1.ServerAuthorization, err error)
// Get retrieves the ServerAuthorization from the indexer for a given namespace and name.
// Objects returned here must be treated as read-only.
Get(name string) (*v1beta1.ServerAuthorization, error)
ServerAuthorizationNamespaceListerExpansion
}
// serverAuthorizationNamespaceLister implements the ServerAuthorizationNamespaceLister
// interface.
type serverAuthorizationNamespaceLister struct {
indexer cache.Indexer
namespace string
}
// List lists all ServerAuthorizations in the indexer for a given namespace.
func (s serverAuthorizationNamespaceLister) List(selector labels.Selector) (ret []*v1beta1.ServerAuthorization, err error) {
err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
ret = append(ret, m.(*v1beta1.ServerAuthorization))
})
return ret, err
}
// Get retrieves the ServerAuthorization from the indexer for a given namespace and name.
func (s serverAuthorizationNamespaceLister) Get(name string) (*v1beta1.ServerAuthorization, error) {
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(v1beta1.Resource("serverauthorization"), name)
}
return obj.(*v1beta1.ServerAuthorization), nil
}
/*
Copyright 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.
*/
// Code generated by lister-gen. DO NOT EDIT.
package v1alpha2
import (
v1alpha2 "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
)
// ServiceProfileLister helps list ServiceProfiles.
// All objects returned here must be treated as read-only.
type ServiceProfileLister interface {
// List lists all ServiceProfiles in the indexer.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1alpha2.ServiceProfile, err error)
// ServiceProfiles returns an object that can list and get ServiceProfiles.
ServiceProfiles(namespace string) ServiceProfileNamespaceLister
ServiceProfileListerExpansion
}
// serviceProfileLister implements the ServiceProfileLister interface.
type serviceProfileLister struct {
indexer cache.Indexer
}
// NewServiceProfileLister returns a new ServiceProfileLister.
func NewServiceProfileLister(indexer cache.Indexer) ServiceProfileLister {
return &serviceProfileLister{indexer: indexer}
}
// List lists all ServiceProfiles in the indexer.
func (s *serviceProfileLister) List(selector labels.Selector) (ret []*v1alpha2.ServiceProfile, err error) {
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha2.ServiceProfile))
})
return ret, err
}
// ServiceProfiles returns an object that can list and get ServiceProfiles.
func (s *serviceProfileLister) ServiceProfiles(namespace string) ServiceProfileNamespaceLister {
return serviceProfileNamespaceLister{indexer: s.indexer, namespace: namespace}
}
// ServiceProfileNamespaceLister helps list and get ServiceProfiles.
// All objects returned here must be treated as read-only.
type ServiceProfileNamespaceLister interface {
// List lists all ServiceProfiles in the indexer for a given namespace.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1alpha2.ServiceProfile, err error)
// Get retrieves the ServiceProfile from the indexer for a given namespace and name.
// Objects returned here must be treated as read-only.
Get(name string) (*v1alpha2.ServiceProfile, error)
ServiceProfileNamespaceListerExpansion
}
// serviceProfileNamespaceLister implements the ServiceProfileNamespaceLister
// interface.
type serviceProfileNamespaceLister struct {
indexer cache.Indexer
namespace string
}
// List lists all ServiceProfiles in the indexer for a given namespace.
func (s serviceProfileNamespaceLister) List(selector labels.Selector) (ret []*v1alpha2.ServiceProfile, err error) {
err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha2.ServiceProfile))
})
return ret, err
}
// Get retrieves the ServiceProfile from the indexer for a given namespace and name.
func (s serviceProfileNamespaceLister) Get(name string) (*v1alpha2.ServiceProfile, error) {
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(v1alpha2.Resource("serviceprofile"), name)
}
return obj.(*v1alpha2.ServiceProfile), nil
}
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc v3.20.0
// source: common/net.proto
package net
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type IPAddress struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Ip:
//
// *IPAddress_Ipv4
// *IPAddress_Ipv6
Ip isIPAddress_Ip `protobuf_oneof:"ip"`
}
func (x *IPAddress) Reset() {
*x = IPAddress{}
if protoimpl.UnsafeEnabled {
mi := &file_common_net_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *IPAddress) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*IPAddress) ProtoMessage() {}
func (x *IPAddress) ProtoReflect() protoreflect.Message {
mi := &file_common_net_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use IPAddress.ProtoReflect.Descriptor instead.
func (*IPAddress) Descriptor() ([]byte, []int) {
return file_common_net_proto_rawDescGZIP(), []int{0}
}
func (m *IPAddress) GetIp() isIPAddress_Ip {
if m != nil {
return m.Ip
}
return nil
}
func (x *IPAddress) GetIpv4() uint32 {
if x, ok := x.GetIp().(*IPAddress_Ipv4); ok {
return x.Ipv4
}
return 0
}
func (x *IPAddress) GetIpv6() *IPv6 {
if x, ok := x.GetIp().(*IPAddress_Ipv6); ok {
return x.Ipv6
}
return nil
}
type isIPAddress_Ip interface {
isIPAddress_Ip()
}
type IPAddress_Ipv4 struct {
Ipv4 uint32 `protobuf:"fixed32,1,opt,name=ipv4,proto3,oneof"`
}
type IPAddress_Ipv6 struct {
Ipv6 *IPv6 `protobuf:"bytes,2,opt,name=ipv6,proto3,oneof"`
}
func (*IPAddress_Ipv4) isIPAddress_Ip() {}
func (*IPAddress_Ipv6) isIPAddress_Ip() {}
type IPv6 struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
First uint64 `protobuf:"fixed64,1,opt,name=first,proto3" json:"first,omitempty"` // hextets 1-4
Last uint64 `protobuf:"fixed64,2,opt,name=last,proto3" json:"last,omitempty"` // hextets 5-8
}
func (x *IPv6) Reset() {
*x = IPv6{}
if protoimpl.UnsafeEnabled {
mi := &file_common_net_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *IPv6) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*IPv6) ProtoMessage() {}
func (x *IPv6) ProtoReflect() protoreflect.Message {
mi := &file_common_net_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use IPv6.ProtoReflect.Descriptor instead.
func (*IPv6) Descriptor() ([]byte, []int) {
return file_common_net_proto_rawDescGZIP(), []int{1}
}
func (x *IPv6) GetFirst() uint64 {
if x != nil {
return x.First
}
return 0
}
func (x *IPv6) GetLast() uint64 {
if x != nil {
return x.Last
}
return 0
}
type TcpAddress struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Ip *IPAddress `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"`
Port uint32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"`
}
func (x *TcpAddress) Reset() {
*x = TcpAddress{}
if protoimpl.UnsafeEnabled {
mi := &file_common_net_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TcpAddress) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TcpAddress) ProtoMessage() {}
func (x *TcpAddress) ProtoReflect() protoreflect.Message {
mi := &file_common_net_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TcpAddress.ProtoReflect.Descriptor instead.
func (*TcpAddress) Descriptor() ([]byte, []int) {
return file_common_net_proto_rawDescGZIP(), []int{2}
}
func (x *TcpAddress) GetIp() *IPAddress {
if x != nil {
return x.Ip
}
return nil
}
func (x *TcpAddress) GetPort() uint32 {
if x != nil {
return x.Port
}
return 0
}
var File_common_net_proto protoreflect.FileDescriptor
var file_common_net_proto_rawDesc = []byte{
0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x12, 0x13, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x63, 0x6f, 0x6d,
0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x22, 0x58, 0x0a, 0x09, 0x49, 0x50, 0x41, 0x64, 0x64,
0x72, 0x65, 0x73, 0x73, 0x12, 0x14, 0x0a, 0x04, 0x69, 0x70, 0x76, 0x34, 0x18, 0x01, 0x20, 0x01,
0x28, 0x07, 0x48, 0x00, 0x52, 0x04, 0x69, 0x70, 0x76, 0x34, 0x12, 0x2f, 0x0a, 0x04, 0x69, 0x70,
0x76, 0x36, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65,
0x72, 0x64, 0x32, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49,
0x50, 0x76, 0x36, 0x48, 0x00, 0x52, 0x04, 0x69, 0x70, 0x76, 0x36, 0x42, 0x04, 0x0a, 0x02, 0x69,
0x70, 0x22, 0x30, 0x0a, 0x04, 0x49, 0x50, 0x76, 0x36, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x69, 0x72,
0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x06, 0x52, 0x05, 0x66, 0x69, 0x72, 0x73, 0x74, 0x12,
0x12, 0x0a, 0x04, 0x6c, 0x61, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x06, 0x52, 0x04, 0x6c,
0x61, 0x73, 0x74, 0x22, 0x50, 0x0a, 0x0a, 0x54, 0x63, 0x70, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73,
0x73, 0x12, 0x2e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e,
0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e,
0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x02, 0x69,
0x70, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52,
0x04, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x37, 0x5a, 0x35, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x2f, 0x6c, 0x69, 0x6e, 0x6b,
0x65, 0x72, 0x64, 0x32, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f,
0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x62, 0x06,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_common_net_proto_rawDescOnce sync.Once
file_common_net_proto_rawDescData = file_common_net_proto_rawDesc
)
func file_common_net_proto_rawDescGZIP() []byte {
file_common_net_proto_rawDescOnce.Do(func() {
file_common_net_proto_rawDescData = protoimpl.X.CompressGZIP(file_common_net_proto_rawDescData)
})
return file_common_net_proto_rawDescData
}
var file_common_net_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_common_net_proto_goTypes = []interface{}{
(*IPAddress)(nil), // 0: linkerd2.common.net.IPAddress
(*IPv6)(nil), // 1: linkerd2.common.net.IPv6
(*TcpAddress)(nil), // 2: linkerd2.common.net.TcpAddress
}
var file_common_net_proto_depIdxs = []int32{
1, // 0: linkerd2.common.net.IPAddress.ipv6:type_name -> linkerd2.common.net.IPv6
0, // 1: linkerd2.common.net.TcpAddress.ip:type_name -> linkerd2.common.net.IPAddress
2, // [2:2] is the sub-list for method output_type
2, // [2:2] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
}
func init() { file_common_net_proto_init() }
func file_common_net_proto_init() {
if File_common_net_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_common_net_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*IPAddress); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_common_net_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*IPv6); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_common_net_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TcpAddress); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_common_net_proto_msgTypes[0].OneofWrappers = []interface{}{
(*IPAddress_Ipv4)(nil),
(*IPAddress_Ipv6)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_common_net_proto_rawDesc,
NumEnums: 0,
NumMessages: 3,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_common_net_proto_goTypes,
DependencyIndexes: file_common_net_proto_depIdxs,
MessageInfos: file_common_net_proto_msgTypes,
}.Build()
File_common_net_proto = out.File
file_common_net_proto_rawDesc = nil
file_common_net_proto_goTypes = nil
file_common_net_proto_depIdxs = nil
}
package k8s
import (
"context"
"fmt"
"strings"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest"
spv1alpha2 "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
l5dcrdclient "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned"
l5dcrdinformer "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions"
ewinformers "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/externalworkload/v1beta1"
srvinformers "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/server/v1beta2"
spinformers "github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/serviceprofile/v1alpha2"
"github.com/linkerd/linkerd2/pkg/k8s"
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/informers"
arinformers "k8s.io/client-go/informers/admissionregistration/v1"
appv1informers "k8s.io/client-go/informers/apps/v1"
batchv1informers "k8s.io/client-go/informers/batch/v1"
coreinformers "k8s.io/client-go/informers/core/v1"
discoveryinformers "k8s.io/client-go/informers/discovery/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
)
// API provides shared informers for all Kubernetes objects
type API struct {
promGauges
Client kubernetes.Interface
DynamicClient dynamic.Interface
cj batchv1informers.CronJobInformer
cm coreinformers.ConfigMapInformer
deploy appv1informers.DeploymentInformer
ds appv1informers.DaemonSetInformer
endpoint coreinformers.EndpointsInformer
es discoveryinformers.EndpointSliceInformer
ew ewinformers.ExternalWorkloadInformer
job batchv1informers.JobInformer
mwc arinformers.MutatingWebhookConfigurationInformer
ns coreinformers.NamespaceInformer
pod coreinformers.PodInformer
rc coreinformers.ReplicationControllerInformer
rs appv1informers.ReplicaSetInformer
sp spinformers.ServiceProfileInformer
ss appv1informers.StatefulSetInformer
svc coreinformers.ServiceInformer
node coreinformers.NodeInformer
secret coreinformers.SecretInformer
srv srvinformers.ServerInformer
syncChecks []cache.InformerSynced
sharedInformers informers.SharedInformerFactory
l5dCrdSharedInformers l5dcrdinformer.SharedInformerFactory
}
// InitializeAPI creates Kubernetes clients and returns an initialized API
// wrapper. This creates informers on each one of resources passed, registering
// metrics on each one; don't forget to call UnregisterGauges() on the returned
// API reference to clean them up!
func InitializeAPI(ctx context.Context, kubeConfig string, ensureClusterWideAccess bool, cluster string, resources ...APIResource) (*API, error) {
config, err := k8s.GetConfig(kubeConfig, "")
if err != nil {
return nil, fmt.Errorf("error configuring Kubernetes API client: %w", err)
}
dynamicClient, err := dynamic.NewForConfig(config)
if err != nil {
return nil, err
}
k8sClient, err := k8s.NewAPIForConfig(config, "", []string{}, 0, 0, 0)
if err != nil {
return nil, err
}
return initAPI(ctx, k8sClient, dynamicClient, config, ensureClusterWideAccess, cluster, resources...)
}
// InitializeAPIForConfig creates Kubernetes clients and returns an initialized
// API wrapper. This creates informers on each one of resources passed,
// registering metrics on each one; don't forget to call UnregisterGauges() on
// the returned API reference to clean them up!
func InitializeAPIForConfig(ctx context.Context, kubeConfig *rest.Config, ensureClusterWideAccess bool, cluster string, resources ...APIResource) (*API, error) {
k8sClient, err := k8s.NewAPIForConfig(kubeConfig, "", []string{}, 0, 0, 0)
if err != nil {
return nil, err
}
return initAPI(ctx, k8sClient, nil, kubeConfig, ensureClusterWideAccess, cluster, resources...)
}
func initAPI(ctx context.Context, k8sClient *k8s.KubernetesAPI, dynamicClient dynamic.Interface, kubeConfig *rest.Config, ensureClusterWideAccess bool, cluster string, resources ...APIResource) (*API, error) {
// check for cluster-wide access
var err error
if ensureClusterWideAccess {
err := k8s.ClusterAccess(ctx, k8sClient)
if err != nil {
return nil, err
}
}
// check for need and access to Linkerd CRD clients
var l5dCrdClient *l5dcrdclient.Clientset
for _, res := range resources {
switch {
case res == SP:
err := k8s.ServiceProfilesAccess(ctx, k8sClient)
if err != nil {
return nil, err
}
case res == Srv:
err := k8s.ServersAccess(ctx, k8sClient)
if err != nil {
return nil, err
}
case res == ExtWorkload:
err := k8s.ExtWorkloadAccess(ctx, k8sClient)
if err != nil {
return nil, err
}
default:
continue
}
l5dCrdClient, err = NewL5DCRDClient(kubeConfig)
if err != nil {
return nil, err
}
break
}
api := NewClusterScopedAPI(k8sClient, dynamicClient, l5dCrdClient, cluster, resources...)
for _, gauge := range api.gauges {
if err := prometheus.Register(gauge); err != nil {
log.Warnf("failed to register Prometheus gauge %s: %s", gauge.Desc().String(), err)
}
}
return api, nil
}
// NewClusterScopedAPI takes a Kubernetes client and returns an initialized
// cluster-wide API. This creates informers on each one of resources passed,
// registering metrics on each one; don't forget to call UnregisterGauges() on
// the returned API reference to clean them up!
func NewClusterScopedAPI(
k8sClient kubernetes.Interface,
dynamicClient dynamic.Interface,
l5dCrdClient l5dcrdclient.Interface,
cluster string,
resources ...APIResource,
) *API {
sharedInformers := informers.NewSharedInformerFactory(k8sClient, ResyncTime)
return newAPI(k8sClient, dynamicClient, l5dCrdClient, sharedInformers, cluster, resources...)
}
// NewNamespacedAPI takes a Kubernetes client and returns an initialized API
// scoped to namespace. This creates informers on each one of resources passed,
// registering metrics on each one; don't forget to call UnregisterGauges() on
// the returned API reference to clean them up!
func NewNamespacedAPI(
k8sClient kubernetes.Interface,
dynamicClient dynamic.Interface,
l5dCrdClient l5dcrdclient.Interface,
namespace string,
cluster string,
resources ...APIResource,
) *API {
sharedInformers := informers.NewSharedInformerFactoryWithOptions(k8sClient, ResyncTime, informers.WithNamespace(namespace))
return newAPI(k8sClient, dynamicClient, l5dCrdClient, sharedInformers, cluster, resources...)
}
// newAPI takes a Kubernetes client and returns an initialized API.
func newAPI(
k8sClient kubernetes.Interface,
dynamicClient dynamic.Interface,
l5dCrdClient l5dcrdclient.Interface,
sharedInformers informers.SharedInformerFactory,
cluster string,
resources ...APIResource,
) *API {
var l5dCrdSharedInformers l5dcrdinformer.SharedInformerFactory
if l5dCrdClient != nil {
l5dCrdSharedInformers = l5dcrdinformer.NewSharedInformerFactory(l5dCrdClient, ResyncTime)
}
api := &API{
Client: k8sClient,
DynamicClient: dynamicClient,
syncChecks: make([]cache.InformerSynced, 0),
sharedInformers: sharedInformers,
l5dCrdSharedInformers: l5dCrdSharedInformers,
}
informerLabels := prometheus.Labels{
"cluster": cluster,
}
for _, resource := range resources {
switch resource {
case CJ:
api.cj = sharedInformers.Batch().V1().CronJobs()
api.syncChecks = append(api.syncChecks, api.cj.Informer().HasSynced)
api.promGauges.addInformerSize(k8s.CronJob, informerLabels, api.cj.Informer())
case CM:
api.cm = sharedInformers.Core().V1().ConfigMaps()
api.syncChecks = append(api.syncChecks, api.cm.Informer().HasSynced)
api.promGauges.addInformerSize(k8s.ConfigMap, informerLabels, api.cm.Informer())
case Deploy:
api.deploy = sharedInformers.Apps().V1().Deployments()
api.syncChecks = append(api.syncChecks, api.deploy.Informer().HasSynced)
api.promGauges.addInformerSize(k8s.Deployment, informerLabels, api.deploy.Informer())
case DS:
api.ds = sharedInformers.Apps().V1().DaemonSets()
api.syncChecks = append(api.syncChecks, api.ds.Informer().HasSynced)
api.promGauges.addInformerSize(k8s.DaemonSet, informerLabels, api.ds.Informer())
case Endpoint:
api.endpoint = sharedInformers.Core().V1().Endpoints()
api.syncChecks = append(api.syncChecks, api.endpoint.Informer().HasSynced)
api.promGauges.addInformerSize(k8s.Endpoints, informerLabels, api.endpoint.Informer())
case ES:
api.es = sharedInformers.Discovery().V1().EndpointSlices()
api.syncChecks = append(api.syncChecks, api.es.Informer().HasSynced)
api.promGauges.addInformerSize(k8s.EndpointSlices, informerLabels, api.es.Informer())
case ExtWorkload:
if l5dCrdSharedInformers == nil {
panic("Linkerd CRD shared informer not configured")
}
api.ew = l5dCrdSharedInformers.Externalworkload().V1beta1().ExternalWorkloads()
api.syncChecks = append(api.syncChecks, api.ew.Informer().HasSynced)
api.promGauges.addInformerSize(k8s.ExtWorkload, informerLabels, api.ew.Informer())
case Job:
api.job = sharedInformers.Batch().V1().Jobs()
api.syncChecks = append(api.syncChecks, api.job.Informer().HasSynced)
api.promGauges.addInformerSize(k8s.Job, informerLabels, api.job.Informer())
case MWC:
api.mwc = sharedInformers.Admissionregistration().V1().MutatingWebhookConfigurations()
api.syncChecks = append(api.syncChecks, api.mwc.Informer().HasSynced)
api.promGauges.addInformerSize(k8s.MutatingWebhookConfig, informerLabels, api.mwc.Informer())
case NS:
api.ns = sharedInformers.Core().V1().Namespaces()
api.syncChecks = append(api.syncChecks, api.ns.Informer().HasSynced)
api.promGauges.addInformerSize(k8s.Namespace, informerLabels, api.ns.Informer())
case Pod:
api.pod = sharedInformers.Core().V1().Pods()
api.syncChecks = append(api.syncChecks, api.pod.Informer().HasSynced)
api.promGauges.addInformerSize(k8s.Pod, informerLabels, api.pod.Informer())
case RC:
api.rc = sharedInformers.Core().V1().ReplicationControllers()
api.syncChecks = append(api.syncChecks, api.rc.Informer().HasSynced)
api.promGauges.addInformerSize(k8s.ReplicationController, informerLabels, api.rc.Informer())
case RS:
api.rs = sharedInformers.Apps().V1().ReplicaSets()
api.syncChecks = append(api.syncChecks, api.rs.Informer().HasSynced)
api.promGauges.addInformerSize(k8s.ReplicaSet, informerLabels, api.rs.Informer())
case SP:
if l5dCrdSharedInformers == nil {
panic("Linkerd CRD shared informer not configured")
}
api.sp = l5dCrdSharedInformers.Linkerd().V1alpha2().ServiceProfiles()
api.syncChecks = append(api.syncChecks, api.sp.Informer().HasSynced)
api.promGauges.addInformerSize(k8s.ServiceProfile, informerLabels, api.sp.Informer())
case Srv:
if l5dCrdSharedInformers == nil {
panic("Linkerd CRD shared informer not configured")
}
api.srv = l5dCrdSharedInformers.Server().V1beta2().Servers()
api.syncChecks = append(api.syncChecks, api.srv.Informer().HasSynced)
api.promGauges.addInformerSize(k8s.Server, informerLabels, api.srv.Informer())
case SS:
api.ss = sharedInformers.Apps().V1().StatefulSets()
api.syncChecks = append(api.syncChecks, api.ss.Informer().HasSynced)
api.promGauges.addInformerSize(k8s.StatefulSet, informerLabels, api.ss.Informer())
case Svc:
api.svc = sharedInformers.Core().V1().Services()
api.syncChecks = append(api.syncChecks, api.svc.Informer().HasSynced)
api.promGauges.addInformerSize(k8s.Service, informerLabels, api.svc.Informer())
case Node:
api.node = sharedInformers.Core().V1().Nodes()
api.syncChecks = append(api.syncChecks, api.node.Informer().HasSynced)
api.promGauges.addInformerSize(k8s.Node, informerLabels, api.node.Informer())
case Secret:
api.secret = sharedInformers.Core().V1().Secrets()
api.syncChecks = append(api.syncChecks, api.secret.Informer().HasSynced)
api.promGauges.addInformerSize(k8s.Secret, informerLabels, api.secret.Informer())
}
}
return api
}
// Sync waits for all informers to be synced.
func (api *API) Sync(stopCh <-chan struct{}) {
api.sharedInformers.Start(stopCh)
if api.l5dCrdSharedInformers != nil {
api.l5dCrdSharedInformers.Start(stopCh)
}
waitForCacheSync(api.syncChecks)
}
// UnregisterGauges unregisters all the prometheus cache gauges associated to this API
func (api *API) UnregisterGauges() {
api.promGauges.unregister()
}
// NS provides access to a shared informer and lister for Namespaces.
func (api *API) NS() coreinformers.NamespaceInformer {
if api.ns == nil {
panic("NS informer not configured")
}
return api.ns
}
// Deploy provides access to a shared informer and lister for Deployments.
func (api *API) Deploy() appv1informers.DeploymentInformer {
if api.deploy == nil {
panic("Deploy informer not configured")
}
return api.deploy
}
// DS provides access to a shared informer and lister for Daemonsets.
func (api *API) DS() appv1informers.DaemonSetInformer {
if api.ds == nil {
panic("DS informer not configured")
}
return api.ds
}
// SS provides access to a shared informer and lister for Statefulsets.
func (api *API) SS() appv1informers.StatefulSetInformer {
if api.ss == nil {
panic("SS informer not configured")
}
return api.ss
}
// RS provides access to a shared informer and lister for ReplicaSets.
func (api *API) RS() appv1informers.ReplicaSetInformer {
if api.rs == nil {
panic("RS informer not configured")
}
return api.rs
}
// Pod provides access to a shared informer and lister for Pods.
func (api *API) Pod() coreinformers.PodInformer {
if api.pod == nil {
panic("Pod informer not configured")
}
return api.pod
}
// RC provides access to a shared informer and lister for
// ReplicationControllers.
func (api *API) RC() coreinformers.ReplicationControllerInformer {
if api.rc == nil {
panic("RC informer not configured")
}
return api.rc
}
// Svc provides access to a shared informer and lister for Services.
func (api *API) Svc() coreinformers.ServiceInformer {
if api.svc == nil {
panic("Svc informer not configured")
}
return api.svc
}
// Endpoint provides access to a shared informer and lister for Endpoints.
func (api *API) Endpoint() coreinformers.EndpointsInformer {
if api.endpoint == nil {
panic("Endpoint informer not configured")
}
return api.endpoint
}
// ES provides access to a shared informer and lister for EndpointSlices
func (api *API) ES() discoveryinformers.EndpointSliceInformer {
if api.es == nil {
panic("EndpointSlices informer not configured")
}
return api.es
}
// ExtWorkload() provides access to a shared informer and lister for
// ExternalWorkload CRDs
func (api *API) ExtWorkload() ewinformers.ExternalWorkloadInformer {
if api.ew == nil {
panic("ExternalWorkload informer not configured")
}
return api.ew
}
// CM provides access to a shared informer and lister for ConfigMaps.
func (api *API) CM() coreinformers.ConfigMapInformer {
if api.cm == nil {
panic("CM informer not configured")
}
return api.cm
}
// SP provides access to a shared informer and lister for ServiceProfiles.
func (api *API) SP() spinformers.ServiceProfileInformer {
if api.sp == nil {
panic("SP informer not configured")
}
return api.sp
}
// Srv provides access to a shared informer and lister for Servers.
func (api *API) Srv() srvinformers.ServerInformer {
if api.srv == nil {
panic("Srv informer not configured")
}
return api.srv
}
// MWC provides access to a shared informer and lister for MutatingWebhookConfigurations.
func (api *API) MWC() arinformers.MutatingWebhookConfigurationInformer {
if api.mwc == nil {
panic("MWC informer not configured")
}
return api.mwc
}
// Job provides access to a shared informer and lister for Jobs.
func (api *API) Job() batchv1informers.JobInformer {
if api.job == nil {
panic("Job informer not configured")
}
return api.job
}
// SPAvailable informs the caller whether this API is configured to retrieve
// ServiceProfiles
func (api *API) SPAvailable() bool {
return api.sp != nil
}
// Node provides access to a shared informer and lister for Nodes.
func (api *API) Node() coreinformers.NodeInformer {
if api.node == nil {
panic("Node informer not configured")
}
return api.node
}
// Secret provides access to a shared informer and lister for Secrets.
func (api *API) Secret() coreinformers.SecretInformer {
if api.secret == nil {
panic("Secret informer not configured")
}
return api.secret
}
// CJ provides access to a shared informer and lister for CronJobs.
func (api *API) CJ() batchv1informers.CronJobInformer {
if api.cj == nil {
panic("CJ informer not configured")
}
return api.cj
}
// GetObjects returns a list of Kubernetes objects, given a namespace, type, name and label selector.
// If namespace is an empty string, match objects in all namespaces.
// If name is an empty string, match all objects of the given type.
// If label selector is an empty string, match all labels.
func (api *API) GetObjects(namespace, restype, name string, label labels.Selector) ([]runtime.Object, error) {
switch restype {
case k8s.Namespace:
return api.getNamespaces(name, label)
case k8s.CronJob:
return api.getCronjobs(namespace, name, label)
case k8s.DaemonSet:
return api.getDaemonsets(namespace, name, label)
case k8s.Deployment:
return api.getDeployments(namespace, name, label)
case k8s.Job:
return api.getJobs(namespace, name, label)
case k8s.Pod:
return api.getPods(namespace, name, label)
case k8s.ReplicationController:
return api.getRCs(namespace, name, label)
case k8s.ReplicaSet:
return api.getReplicasets(namespace, name, label)
case k8s.Service:
return api.getServices(namespace, name)
case k8s.StatefulSet:
return api.getStatefulsets(namespace, name, label)
default:
return nil, status.Errorf(codes.Unimplemented, "unimplemented resource type: %s", restype)
}
}
// KindSupported returns true if there is an informer configured for the
// specified resource type.
func (api *API) KindSupported(restype string) bool {
switch restype {
case k8s.Namespace:
return api.ns != nil
case k8s.CronJob:
return api.cj != nil
case k8s.DaemonSet:
return api.ds != nil
case k8s.Deployment:
return api.deploy != nil
case k8s.Job:
return api.job != nil
case k8s.Pod:
return api.pod != nil
case k8s.ReplicationController:
return api.rc != nil
case k8s.ReplicaSet:
return api.rs != nil
case k8s.Service:
return api.svc != nil
case k8s.StatefulSet:
return api.ss != nil
default:
return false
}
}
// GetOwnerKindAndName returns the pod owner's kind and name, using owner
// references from the Kubernetes API. The kind is represented as the Kubernetes
// singular resource type (e.g. deployment, daemonset, job, etc.).
// If retry is true, when the shared informer cache doesn't return anything
// we try again with a direct Kubernetes API call.
func (api *API) GetOwnerKindAndName(ctx context.Context, pod *corev1.Pod, retry bool) (string, string) {
ownerRefs := pod.GetOwnerReferences()
if len(ownerRefs) == 0 {
// pod without a parent
return k8s.Pod, pod.Name
} else if len(ownerRefs) > 1 {
log.Debugf("unexpected owner reference count (%d): %+v", len(ownerRefs), ownerRefs)
return k8s.Pod, pod.Name
}
parent := ownerRefs[0]
var parentObj metav1.Object
var err error
switch parent.Kind {
case "Job":
parentObj, err = api.Job().Lister().Jobs(pod.Namespace).Get(parent.Name)
if err != nil {
log.Warnf("failed to retrieve job from indexer %s/%s: %s", pod.Namespace, parent.Name, err)
if retry {
parentObj, err = api.Client.BatchV1().Jobs(pod.Namespace).Get(ctx, parent.Name, metav1.GetOptions{})
if err != nil {
log.Warnf("failed to retrieve job from direct API call %s/%s: %s", pod.Namespace, parent.Name, err)
}
}
}
case "ReplicaSet":
rsObj, err := api.RS().Lister().ReplicaSets(pod.Namespace).Get(parent.Name)
if err != nil {
log.Warnf("failed to retrieve replicaset from indexer %s/%s: %s", pod.Namespace, parent.Name, err)
if retry {
rsObj, err = api.Client.AppsV1().ReplicaSets(pod.Namespace).Get(ctx, parent.Name, metav1.GetOptions{})
if err != nil {
log.Warnf("failed to retrieve replicaset from direct API call %s/%s: %s", pod.Namespace, parent.Name, err)
}
}
}
if rsObj == nil || !isValidRSParent(rsObj.GetObjectMeta()) {
return strings.ToLower(parent.Kind), parent.Name
}
parentObj = rsObj
default:
return strings.ToLower(parent.Kind), parent.Name
}
if err == nil && len(parentObj.GetOwnerReferences()) == 1 {
grandParent := parentObj.GetOwnerReferences()[0]
return strings.ToLower(grandParent.Kind), grandParent.Name
}
return strings.ToLower(parent.Kind), parent.Name
}
// GetPodsFor returns all running and pending Pods associated with a given
// Kubernetes object. Use includeFailed to also get failed Pods
func (api *API) GetPodsFor(obj runtime.Object, includeFailed bool) ([]*corev1.Pod, error) {
var namespace string
var selector labels.Selector
var ownerUID types.UID
var err error
pods := []*corev1.Pod{}
switch typed := obj.(type) {
case *corev1.Namespace:
namespace = typed.Name
selector = labels.Everything()
case *batchv1.CronJob:
namespace = typed.Namespace
selector = labels.Everything()
jobs, err := api.Job().Lister().Jobs(namespace).List(selector)
if err != nil {
return nil, err
}
for _, job := range jobs {
if isOwner(typed.UID, job.GetOwnerReferences()) {
jobPods, err := api.GetPodsFor(job, includeFailed)
if err != nil {
return nil, err
}
pods = append(pods, jobPods...)
}
}
return pods, nil
case *appsv1.DaemonSet:
namespace = typed.Namespace
selector = labels.Set(typed.Spec.Selector.MatchLabels).AsSelector()
ownerUID = typed.UID
case *appsv1.Deployment:
namespace = typed.Namespace
selector = labels.Set(typed.Spec.Selector.MatchLabels).AsSelector()
ret, err := api.RS().Lister().ReplicaSets(namespace).List(selector)
if err != nil {
return nil, err
}
for _, rs := range ret {
if isOwner(typed.UID, rs.GetOwnerReferences()) {
podsRS, err := api.GetPodsFor(rs, includeFailed)
if err != nil {
return nil, err
}
pods = append(pods, podsRS...)
}
}
return pods, nil
case *appsv1.ReplicaSet:
namespace = typed.Namespace
selector = labels.Set(typed.Spec.Selector.MatchLabels).AsSelector()
ownerUID = typed.UID
case *batchv1.Job:
namespace = typed.Namespace
selector = labels.Set(typed.Spec.Selector.MatchLabels).AsSelector()
ownerUID = typed.UID
case *corev1.ReplicationController:
namespace = typed.Namespace
selector = labels.Set(typed.Spec.Selector).AsSelector()
ownerUID = typed.UID
case *corev1.Service:
if typed.Spec.Type == corev1.ServiceTypeExternalName {
return []*corev1.Pod{}, nil
}
namespace = typed.Namespace
if typed.Spec.Selector == nil {
selector = labels.Nothing()
} else {
selector = labels.Set(typed.Spec.Selector).AsSelector()
}
case *appsv1.StatefulSet:
namespace = typed.Namespace
selector = labels.Set(typed.Spec.Selector.MatchLabels).AsSelector()
ownerUID = typed.UID
case *corev1.Pod:
// Special case for pods:
// GetPodsFor a pod should just return the pod itself
namespace = typed.Namespace
pod, err := api.Pod().Lister().Pods(typed.Namespace).Get(typed.Name)
if err != nil {
return nil, err
}
pods = []*corev1.Pod{pod}
default:
return nil, fmt.Errorf("Cannot get object selector: %v", obj)
}
// if obj.(type) is Pod, we've already retrieved it and put it in pods
// for the other types, pods will still be empty
if len(pods) == 0 {
pods, err = api.Pod().Lister().Pods(namespace).List(selector)
if err != nil {
return nil, err
}
}
allPods := []*corev1.Pod{}
for _, pod := range pods {
if isPendingOrRunning(pod) || (includeFailed && isFailed(pod)) {
if ownerUID == "" || isOwner(ownerUID, pod.GetOwnerReferences()) {
allPods = append(allPods, pod)
}
}
}
return allPods, nil
}
func isOwner(u types.UID, ownerRefs []metav1.OwnerReference) bool {
for _, or := range ownerRefs {
if u == or.UID {
return true
}
}
return false
}
// GetNameAndNamespaceOf returns the name and namespace of the given object.
func GetNameAndNamespaceOf(obj runtime.Object) (string, string, error) {
switch typed := obj.(type) {
case *corev1.Namespace:
return typed.Name, typed.Name, nil
case *batchv1.CronJob:
return typed.Name, typed.Namespace, nil
case *appsv1.DaemonSet:
return typed.Name, typed.Namespace, nil
case *appsv1.Deployment:
return typed.Name, typed.Namespace, nil
case *batchv1.Job:
return typed.Name, typed.Namespace, nil
case *appsv1.ReplicaSet:
return typed.Name, typed.Namespace, nil
case *corev1.ReplicationController:
return typed.Name, typed.Namespace, nil
case *corev1.Service:
return typed.Name, typed.Namespace, nil
case *appsv1.StatefulSet:
return typed.Name, typed.Namespace, nil
case *corev1.Pod:
return typed.Name, typed.Namespace, nil
default:
return "", "", fmt.Errorf("Cannot determine object type: %v", obj)
}
}
// GetNameOf returns the name of the given object.
func GetNameOf(obj runtime.Object) (string, error) {
name, _, err := GetNameAndNamespaceOf(obj)
if err != nil {
return "", err
}
return name, nil
}
// GetNamespaceOf returns the namespace of the given object.
func GetNamespaceOf(obj runtime.Object) (string, error) {
_, namespace, err := GetNameAndNamespaceOf(obj)
if err != nil {
return "", err
}
return namespace, nil
}
// getNamespaces returns the namespace matching the specified name. If no name
// is given, it returns all namespaces.
func (api *API) getNamespaces(name string, labelSelector labels.Selector) ([]runtime.Object, error) {
var namespaces []*corev1.Namespace
if name == "" {
var err error
namespaces, err = api.NS().Lister().List(labelSelector)
if err != nil {
return nil, err
}
} else {
namespace, err := api.NS().Lister().Get(name)
if err != nil {
return nil, err
}
namespaces = []*corev1.Namespace{namespace}
}
objects := []runtime.Object{}
for _, ns := range namespaces {
objects = append(objects, ns)
}
return objects, nil
}
func (api *API) getDeployments(namespace, name string, labelSelector labels.Selector) ([]runtime.Object, error) {
var err error
var deploys []*appsv1.Deployment
if namespace == "" {
deploys, err = api.Deploy().Lister().List(labelSelector)
} else if name == "" {
deploys, err = api.Deploy().Lister().Deployments(namespace).List(labelSelector)
} else {
var deploy *appsv1.Deployment
deploy, err = api.Deploy().Lister().Deployments(namespace).Get(name)
deploys = []*appsv1.Deployment{deploy}
}
if err != nil {
return nil, err
}
objects := []runtime.Object{}
for _, deploy := range deploys {
objects = append(objects, deploy)
}
return objects, nil
}
func (api *API) getPods(namespace, name string, labelSelector labels.Selector) ([]runtime.Object, error) {
var err error
var pods []*corev1.Pod
if namespace == "" {
pods, err = api.Pod().Lister().List(labelSelector)
} else if name == "" {
pods, err = api.Pod().Lister().Pods(namespace).List(labelSelector)
} else {
var pod *corev1.Pod
pod, err = api.Pod().Lister().Pods(namespace).Get(name)
pods = []*corev1.Pod{pod}
}
if err != nil {
return nil, err
}
objects := []runtime.Object{}
for _, pod := range pods {
if !isPendingOrRunning(pod) {
continue
}
objects = append(objects, pod)
}
return objects, nil
}
func (api *API) getRCs(namespace, name string, labelSelector labels.Selector) ([]runtime.Object, error) {
var err error
var rcs []*corev1.ReplicationController
if namespace == "" {
rcs, err = api.RC().Lister().List(labelSelector)
} else if name == "" {
rcs, err = api.RC().Lister().ReplicationControllers(namespace).List(labelSelector)
} else {
var rc *corev1.ReplicationController
rc, err = api.RC().Lister().ReplicationControllers(namespace).Get(name)
rcs = []*corev1.ReplicationController{rc}
}
if err != nil {
return nil, err
}
objects := []runtime.Object{}
for _, rc := range rcs {
objects = append(objects, rc)
}
return objects, nil
}
func (api *API) getDaemonsets(namespace, name string, labelSelector labels.Selector) ([]runtime.Object, error) {
var err error
var daemonsets []*appsv1.DaemonSet
if namespace == "" {
daemonsets, err = api.DS().Lister().List(labelSelector)
} else if name == "" {
daemonsets, err = api.DS().Lister().DaemonSets(namespace).List(labelSelector)
} else {
var ds *appsv1.DaemonSet
ds, err = api.DS().Lister().DaemonSets(namespace).Get(name)
daemonsets = []*appsv1.DaemonSet{ds}
}
if err != nil {
return nil, err
}
objects := []runtime.Object{}
for _, ds := range daemonsets {
objects = append(objects, ds)
}
return objects, nil
}
func (api *API) getStatefulsets(namespace, name string, labelSelector labels.Selector) ([]runtime.Object, error) {
var err error
var statefulsets []*appsv1.StatefulSet
if namespace == "" {
statefulsets, err = api.SS().Lister().List(labelSelector)
} else if name == "" {
statefulsets, err = api.SS().Lister().StatefulSets(namespace).List(labelSelector)
} else {
var ss *appsv1.StatefulSet
ss, err = api.SS().Lister().StatefulSets(namespace).Get(name)
statefulsets = []*appsv1.StatefulSet{ss}
}
if err != nil {
return nil, err
}
objects := []runtime.Object{}
for _, ss := range statefulsets {
objects = append(objects, ss)
}
return objects, nil
}
func (api *API) getJobs(namespace, name string, labelSelector labels.Selector) ([]runtime.Object, error) {
var err error
var jobs []*batchv1.Job
if namespace == "" {
jobs, err = api.Job().Lister().List(labelSelector)
} else if name == "" {
jobs, err = api.Job().Lister().Jobs(namespace).List(labelSelector)
} else {
var job *batchv1.Job
job, err = api.Job().Lister().Jobs(namespace).Get(name)
jobs = []*batchv1.Job{job}
}
if err != nil {
return nil, err
}
objects := []runtime.Object{}
for _, job := range jobs {
objects = append(objects, job)
}
return objects, nil
}
func (api *API) getServices(namespace, name string) ([]runtime.Object, error) {
services, err := api.GetServices(namespace, name)
if err != nil {
return nil, err
}
objects := []runtime.Object{}
for _, svc := range services {
objects = append(objects, svc)
}
return objects, nil
}
func (api *API) getCronjobs(namespace, name string, labelSelector labels.Selector) ([]runtime.Object, error) {
var err error
var cronjobs []*batchv1.CronJob
if namespace == "" {
cronjobs, err = api.CJ().Lister().List(labelSelector)
} else if name == "" {
cronjobs, err = api.CJ().Lister().CronJobs(namespace).List(labelSelector)
} else {
var cronjob *batchv1.CronJob
cronjob, err = api.CJ().Lister().CronJobs(namespace).Get(name)
cronjobs = []*batchv1.CronJob{cronjob}
}
if err != nil {
return nil, err
}
objects := []runtime.Object{}
for _, cronjob := range cronjobs {
objects = append(objects, cronjob)
}
return objects, nil
}
func (api *API) getReplicasets(namespace, name string, labelSelector labels.Selector) ([]runtime.Object, error) {
var err error
var replicasets []*appsv1.ReplicaSet
if namespace == "" {
replicasets, err = api.RS().Lister().List(labelSelector)
} else if name == "" {
replicasets, err = api.RS().Lister().ReplicaSets(namespace).List(labelSelector)
} else {
var replicaset *appsv1.ReplicaSet
replicaset, err = api.RS().Lister().ReplicaSets(namespace).Get(name)
replicasets = []*appsv1.ReplicaSet{replicaset}
}
if err != nil {
return nil, err
}
objects := []runtime.Object{}
for _, replicaset := range replicasets {
objects = append(objects, replicaset)
}
return objects, nil
}
// GetServices returns a list of Service resources, based on input namespace and
// name.
func (api *API) GetServices(namespace, name string) ([]*corev1.Service, error) {
var err error
var services []*corev1.Service
if namespace == "" {
services, err = api.Svc().Lister().List(labels.Everything())
} else if name == "" {
services, err = api.Svc().Lister().Services(namespace).List(labels.Everything())
} else {
var svc *corev1.Service
svc, err = api.Svc().Lister().Services(namespace).Get(name)
services = []*corev1.Service{svc}
}
return services, err
}
// GetServicesFor returns all Service resources which include a pod of the given
// resource object. In other words, it returns all Services of which the given
// resource object is a part of.
func (api *API) GetServicesFor(obj runtime.Object, includeFailed bool) ([]*corev1.Service, error) {
if svc, ok := obj.(*corev1.Service); ok {
return []*corev1.Service{svc}, nil
}
pods, err := api.GetPodsFor(obj, includeFailed)
if err != nil {
return nil, err
}
namespace, err := GetNamespaceOf(obj)
if err != nil {
return nil, err
}
allServices, err := api.GetServices(namespace, "")
if err != nil {
return nil, err
}
services := make([]*corev1.Service, 0)
for _, svc := range allServices {
svcPods, err := api.GetPodsFor(svc, includeFailed)
if err != nil {
return nil, err
}
if hasOverlap(pods, svcPods) {
services = append(services, svc)
}
}
return services, nil
}
// GetServiceProfileFor returns the service profile for a given service. We
// first look for a matching service profile in the client's namespace. If not
// found, we then look in the service's namespace. If no service profile is
// found, we return the default service profile.
func (api *API) GetServiceProfileFor(svc *corev1.Service, clientNs, clusterDomain string) *spv1alpha2.ServiceProfile {
dst := fmt.Sprintf("%s.%s.svc.%s", svc.Name, svc.Namespace, clusterDomain)
// First attempt to lookup profile in client namespace
if clientNs != "" {
p, err := api.SP().Lister().ServiceProfiles(clientNs).Get(dst)
if err == nil {
return p
}
if !apierrors.IsNotFound(err) {
log.Errorf("error getting service profile for %s in %s namespace: %s", dst, clientNs, err)
}
}
// Second, attempt to lookup profile in server namespace
if svc.Namespace != clientNs {
p, err := api.SP().Lister().ServiceProfiles(svc.Namespace).Get(dst)
if err == nil {
return p
}
if !apierrors.IsNotFound(err) {
log.Errorf("error getting service profile for %s in %s namespace: %s", dst, svc.Namespace, err)
}
}
// Not found; return default.
log.Debugf("no Service Profile found for '%s' -- using default", dst)
return &spv1alpha2.ServiceProfile{
ObjectMeta: metav1.ObjectMeta{
Name: dst,
},
Spec: spv1alpha2.ServiceProfileSpec{
Routes: []*spv1alpha2.RouteSpec{},
},
}
}
func hasOverlap(as, bs []*corev1.Pod) bool {
for _, a := range as {
for _, b := range bs {
if a.Name == b.Name {
return true
}
}
}
return false
}
func isPendingOrRunning(pod *corev1.Pod) bool {
pending := pod.Status.Phase == corev1.PodPending
running := pod.Status.Phase == corev1.PodRunning
terminating := pod.DeletionTimestamp != nil
return (pending || running) && !terminating
}
func isFailed(pod *corev1.Pod) bool {
return pod.Status.Phase == corev1.PodFailed
}
package k8s
import (
"strings"
serverv1beta2 "github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta2"
sazv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/serverauthorization/v1beta1"
spv1alpha2 "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
"github.com/linkerd/linkerd2/pkg/k8s"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
v1 "k8s.io/api/core/v1"
discoveryv1 "k8s.io/api/discovery/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// APIResource is an enum for Kubernetes API resource types, for use when
// initializing a K8s API, to describe which resource types to interact with.
type APIResource int
// These constants enumerate Kubernetes resource types.
// TODO: Unify with the resources listed in pkg/k8s/k8s.go
const (
CJ APIResource = iota
CM
Deploy
DS
Endpoint
ES // EndpointSlice resource
ExtWorkload
Job
MWC
NS
Pod
RC
RS
SP
SS
Svc
Node
Secret
Srv
Saz
)
// GVK returns the GroupVersionKind corresponding for the provided APIResource
func (res APIResource) GVK() (schema.GroupVersionKind, error) {
switch res {
case CJ:
return batchv1.SchemeGroupVersion.WithKind("CronJob"), nil
case CM:
return v1.SchemeGroupVersion.WithKind("ConfigMap"), nil
case Deploy:
return appsv1.SchemeGroupVersion.WithKind("Deployment"), nil
case DS:
return appsv1.SchemeGroupVersion.WithKind("DaemonSet"), nil
case Endpoint:
return v1.SchemeGroupVersion.WithKind("Endpoint"), nil
case ES:
return discoveryv1.SchemeGroupVersion.WithKind("EndpointSlice"), nil
case Job:
return batchv1.SchemeGroupVersion.WithKind("Job"), nil
case MWC:
return admissionregistrationv1.SchemeGroupVersion.WithKind("MutatingWebhookConfiguration"), nil
case Node:
return v1.SchemeGroupVersion.WithKind("Node"), nil
case NS:
return v1.SchemeGroupVersion.WithKind("Namespace"), nil
case Pod:
return v1.SchemeGroupVersion.WithKind("Pod"), nil
case RC:
return v1.SchemeGroupVersion.WithKind("ReplicationController"), nil
case RS:
return appsv1.SchemeGroupVersion.WithKind("ReplicaSet"), nil
case Saz:
return sazv1beta1.SchemeGroupVersion.WithKind("ServerAuthorization"), nil
case Secret:
return v1.SchemeGroupVersion.WithKind("Secret"), nil
case SP:
return spv1alpha2.SchemeGroupVersion.WithKind("ServiceProfile"), nil
case SS:
return appsv1.SchemeGroupVersion.WithKind("StatefulSet"), nil
case Srv:
return serverv1beta2.SchemeGroupVersion.WithKind("Server"), nil
case Svc:
return v1.SchemeGroupVersion.WithKind("Service"), nil
default:
return schema.GroupVersionKind{}, status.Errorf(codes.Unimplemented, "unimplemented resource type: %d", res)
}
}
// GetAPIResource returns the APIResource for the provided kind
func GetAPIResource(kind string) (APIResource, error) {
switch strings.ToLower(kind) {
case k8s.CronJob:
return CJ, nil
case k8s.ConfigMap:
return CM, nil
case k8s.Deployment:
return Deploy, nil
case k8s.DaemonSet:
return DS, nil
case k8s.Endpoints:
return Endpoint, nil
case k8s.EndpointSlices:
return ES, nil
case k8s.Job:
return Job, nil
case k8s.MutatingWebhookConfig:
return MWC, nil
case k8s.Namespace:
return NS, nil
case k8s.Node:
return Node, nil
case k8s.Pod:
return Pod, nil
case k8s.ReplicationController:
return RC, nil
case k8s.ReplicaSet:
return RS, nil
case k8s.ServerAuthorization:
return Saz, nil
case k8s.Secret:
return Secret, nil
case k8s.ServiceProfile:
return SP, nil
case k8s.Service:
return Svc, nil
case k8s.StatefulSet:
return SS, nil
case k8s.Server:
return Srv, nil
default:
return 0, status.Errorf(codes.Unimplemented, "unimplemented resource type: %s", kind)
}
}
package k8s
import (
l5dcrdclient "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned"
"github.com/linkerd/linkerd2/pkg/prometheus"
"k8s.io/client-go/rest"
// Load all the auth plugins for the cloud providers.
_ "k8s.io/client-go/plugin/pkg/client/auth"
)
func wrapTransport(config *rest.Config, telemetryName string) *rest.Config {
wt := config.WrapTransport
config.WrapTransport = prometheus.ClientWithTelemetry(telemetryName, wt)
return config
}
// NewL5DCRDClient returns a Linkerd controller client for the given
// configuration.
func NewL5DCRDClient(kubeConfig *rest.Config) (*l5dcrdclient.Clientset, error) {
return l5dcrdclient.NewForConfig(wrapTransport(kubeConfig, "l5dCrd"))
}
package k8s
import (
"context"
"strings"
"time"
"github.com/linkerd/linkerd2/pkg/k8s"
log "github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/cache"
)
const ResyncTime = 10 * time.Minute
func waitForCacheSync(syncChecks []cache.InformerSynced) {
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
log.Infof("waiting for caches to sync")
if !cache.WaitForCacheSync(ctx.Done(), syncChecks...) {
//nolint:gocritic
log.Fatal("failed to sync caches")
}
log.Infof("caches synced")
}
func isValidRSParent(rs metav1.Object) bool {
if len(rs.GetOwnerReferences()) != 1 {
return false
}
validParentKinds := []string{
k8s.Job,
k8s.StatefulSet,
k8s.DaemonSet,
k8s.Deployment,
}
rsOwner := rs.GetOwnerReferences()[0]
rsOwnerKind := strings.ToLower(rsOwner.Kind)
for _, kind := range validParentKinds {
if rsOwnerKind == kind {
return true
}
}
return false
}
package k8s
import (
"context"
"fmt"
"strings"
"github.com/linkerd/linkerd2/pkg/k8s"
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/informers"
"k8s.io/client-go/metadata"
"k8s.io/client-go/metadata/metadatainformer"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/cache"
)
// MetadataAPI provides shared metadata informers for some Kubernetes resources
type MetadataAPI struct {
promGauges
client metadata.Interface
inf map[APIResource]informers.GenericInformer
syncChecks []cache.InformerSynced
sharedInformers metadatainformer.SharedInformerFactory
}
// InitializeMetadataAPI returns an instance of MetadataAPI with metadata
// informers for the provided resources
func InitializeMetadataAPI(kubeConfig string, cluster string, resources ...APIResource) (*MetadataAPI, error) {
config, err := k8s.GetConfig(kubeConfig, "")
if err != nil {
return nil, fmt.Errorf("error configuring Kubernetes API client: %w", err)
}
return InitializeMetadataAPIForConfig(config, cluster, resources...)
}
func InitializeMetadataAPIForConfig(kubeConfig *rest.Config, cluster string, resources ...APIResource) (*MetadataAPI, error) {
client, err := metadata.NewForConfig(kubeConfig)
if err != nil {
return nil, err
}
api, err := newClusterScopedMetadataAPI(client, cluster, resources...)
if err != nil {
return nil, err
}
for _, gauge := range api.gauges {
if err := prometheus.Register(gauge); err != nil {
log.Warnf("failed to register Prometheus gauge %s: %s", gauge.Desc().String(), err)
}
}
return api, nil
}
func newClusterScopedMetadataAPI(
metadataClient metadata.Interface,
cluster string,
resources ...APIResource,
) (*MetadataAPI, error) {
sharedInformers := metadatainformer.NewFilteredSharedInformerFactory(
metadataClient,
ResyncTime,
metav1.NamespaceAll,
nil,
)
api := &MetadataAPI{
client: metadataClient,
inf: make(map[APIResource]informers.GenericInformer),
syncChecks: make([]cache.InformerSynced, 0),
sharedInformers: sharedInformers,
}
informerLabels := prometheus.Labels{
"cluster": cluster,
}
for _, resource := range resources {
if err := api.addInformer(resource, informerLabels); err != nil {
return nil, err
}
}
return api, nil
}
// Sync waits for all informers to be synced.
func (api *MetadataAPI) Sync(stopCh <-chan struct{}) {
api.sharedInformers.Start(stopCh)
waitForCacheSync(api.syncChecks)
}
// UnregisterGauges unregisters all the prometheus cache gauges associated to this API
func (api *MetadataAPI) UnregisterGauges() {
api.promGauges.unregister()
}
func (api *MetadataAPI) getLister(res APIResource) (cache.GenericLister, error) {
inf, ok := api.inf[res]
if !ok {
return nil, fmt.Errorf("metadata informer (%v) not configured", res)
}
return inf.Lister(), nil
}
// Get returns the metadata for the supplied object type and name. This uses a
// shared informer and the results may be out of date if the informer is
// lagging behind.
func (api *MetadataAPI) Get(res APIResource, name string) (*metav1.PartialObjectMetadata, error) {
ls, err := api.getLister(res)
if err != nil {
return nil, err
}
obj, err := ls.Get(name)
if err != nil {
return nil, err
}
// ls' concrete type is metadatalister.metadataListerShim, whose
// Get method always returns *metav1.PartialObjectMetadata
nsMeta, ok := obj.(*metav1.PartialObjectMetadata)
if !ok {
return nil, fmt.Errorf("couldn't convert obj %v to PartialObjectMetadata", obj)
}
return nsMeta, nil
}
func (api *MetadataAPI) getByNamespace(res APIResource, ns, name string) (*metav1.PartialObjectMetadata, error) {
ls, err := api.getLister(res)
if err != nil {
return nil, err
}
obj, err := ls.ByNamespace(ns).Get(name)
if err != nil {
return nil, err
}
nsMeta, ok := obj.(*metav1.PartialObjectMetadata)
if !ok {
return nil, fmt.Errorf("couldn't convert obj %v to PartialObjectMetadata", obj)
}
return nsMeta, nil
}
// GetByNamespaceFiltered returns a list of Kubernetes object references, given
// a type, namespace, name and label selector. This uses a shared informer and
// the results may be out of date if the informer is lagging behind.
func (api *MetadataAPI) GetByNamespaceFiltered(
restype APIResource,
ns string,
name string,
labelSelector labels.Selector,
) ([]*metav1.PartialObjectMetadata, error) {
ls, err := api.getLister(restype)
if err != nil {
return nil, err
}
var objs []runtime.Object
if ns == "" {
objs, err = ls.List(labelSelector)
} else if name == "" {
objs, err = ls.ByNamespace(ns).List(labelSelector)
} else {
var obj runtime.Object
obj, err = ls.ByNamespace(ns).Get(name)
objs = []runtime.Object{obj}
}
if err != nil {
return nil, err
}
objMetas := []*metav1.PartialObjectMetadata{}
for _, obj := range objs {
// ls' concrete type is metadatalister.metadataListerShim, which
// guarantees this cast won't fail
objMeta, ok := obj.(*metav1.PartialObjectMetadata)
if !ok {
return nil, fmt.Errorf("couldn't convert obj %v to PartialObjectMetadata", obj)
}
gvk, err := restype.GVK()
if err != nil {
return nil, err
}
// objMeta's TypeMeta fields aren't getting populated, so we do it
// manually here
objMeta.SetGroupVersionKind(gvk)
objMetas = append(objMetas, objMeta)
}
return objMetas, nil
}
// GetOwnerKindAndName returns the pod owner's kind and name, using owner
// references from the Kubernetes API. The kind is represented as the
// Kubernetes singular resource type (e.g. deployment, daemonset, job, etc.).
// If retry is true, when the shared informer cache doesn't return anything we
// try again with a direct Kubernetes API call.
func (api *MetadataAPI) GetOwnerKindAndName(ctx context.Context, pod *corev1.Pod, retry bool) (string, string, error) {
ownerRefs := pod.GetOwnerReferences()
if len(ownerRefs) == 0 {
// pod without a parent
return "pod", pod.Name, nil
} else if len(ownerRefs) > 1 {
log.Debugf("unexpected owner reference count (%d): %+v", len(ownerRefs), ownerRefs)
return "pod", pod.Name, nil
}
parent := ownerRefs[0]
var parentObj metav1.Object
var err error
switch parent.Kind {
case "Job":
parentObj, err = api.getByNamespace(Job, pod.Namespace, parent.Name)
if err != nil {
log.Warnf("failed to retrieve job from indexer %s/%s: %s", pod.Namespace, parent.Name, err)
if retry {
parentObj, err = api.client.
Resource(batchv1.SchemeGroupVersion.WithResource("jobs")).
Namespace(pod.Namespace).
Get(ctx, parent.Name, metav1.GetOptions{})
if err != nil {
log.Warnf("failed to retrieve job from direct API call %s/%s: %s", pod.Namespace, parent.Name, err)
}
}
}
case "ReplicaSet":
var rsObj *metav1.PartialObjectMetadata
rsObj, err = api.getByNamespace(RS, pod.Namespace, parent.Name)
if err != nil {
log.Warnf("failed to retrieve replicaset from indexer %s/%s: %s", pod.Namespace, parent.Name, err)
if retry {
rsObj, err = api.client.
Resource(appsv1.SchemeGroupVersion.WithResource("replicasets")).
Namespace(pod.Namespace).
Get(ctx, parent.Name, metav1.GetOptions{})
if err != nil {
log.Warnf("failed to retrieve replicaset from direct API call %s/%s: %s", pod.Namespace, parent.Name, err)
}
}
}
if rsObj == nil || !isValidRSParent(rsObj) {
return strings.ToLower(parent.Kind), parent.Name, nil
}
parentObj = rsObj
default:
return strings.ToLower(parent.Kind), parent.Name, nil
}
if err == nil && len(parentObj.GetOwnerReferences()) == 1 {
grandParent := parentObj.GetOwnerReferences()[0]
return strings.ToLower(grandParent.Kind), grandParent.Name, nil
}
return strings.ToLower(parent.Kind), parent.Name, nil
}
func (api *MetadataAPI) addInformer(res APIResource, informerLabels prometheus.Labels) error {
gvk, err := res.GVK()
if err != nil {
return err
}
gvr, _ := meta.UnsafeGuessKindToResource(gvk)
inf := api.sharedInformers.ForResource(gvr)
api.syncChecks = append(api.syncChecks, inf.Informer().HasSynced)
api.promGauges.addInformerSize(strings.ToLower(gvk.Kind), informerLabels, inf.Informer())
api.inf[res] = inf
return nil
}
package k8s
import (
"fmt"
"github.com/prometheus/client_golang/prometheus"
"k8s.io/client-go/tools/cache"
)
type promGauges struct {
gauges []prometheus.GaugeFunc
}
func (p *promGauges) addInformerSize(kind string, labels prometheus.Labels, inf cache.SharedIndexInformer) {
p.gauges = append(p.gauges, prometheus.NewGaugeFunc(prometheus.GaugeOpts{
Name: fmt.Sprintf("%s_cache_size", kind),
Help: fmt.Sprintf("Number of items in the client-go %s cache", kind),
ConstLabels: labels,
}, func() float64 {
return float64(len(inf.GetStore().ListKeys()))
}))
}
func (p *promGauges) unregister() {
for _, gauge := range p.gauges {
prometheus.Unregister(gauge)
}
}
package k8s
import (
l5dcrdclient "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned"
"github.com/linkerd/linkerd2/pkg/k8s"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"
clientsetscheme "k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/metadata/fake"
"k8s.io/client-go/testing"
)
// NewFakeAPI provides a mock Kubernetes API for testing.
func NewFakeAPI(configs ...string) (*API, error) {
clientSet, _, _, spClientSet, err := k8s.NewFakeClientSets(configs...)
if err != nil {
return nil, err
}
return NewFakeClusterScopedAPI(clientSet, spClientSet), nil
}
// NewFakeAPI provides a mock Kubernetes API for testing.
func NewFakeAPIWithActions(configs ...string) (*API, func() []testing.Action, error) {
clientSet, _, _, spClientSet, err := k8s.NewFakeClientSets(configs...)
if err != nil {
return nil, nil, err
}
return NewFakeClusterScopedAPI(clientSet, spClientSet), clientSet.Actions, nil
}
// NewFakeAPIWithL5dClient provides a mock Kubernetes API for testing like
// NewFakeAPI, but it also returns the mock client for linkerd CRDs
func NewFakeAPIWithL5dClient(configs ...string) (*API, l5dcrdclient.Interface, error) {
clientSet, _, _, l5dClientSet, err := k8s.NewFakeClientSets(configs...)
if err != nil {
return nil, nil, err
}
return NewFakeClusterScopedAPI(clientSet, l5dClientSet), l5dClientSet, nil
}
// NewFakeClusterScopedAPI provides a mock Kubernetes API for testing.
func NewFakeClusterScopedAPI(clientSet kubernetes.Interface, l5dClientSet l5dcrdclient.Interface) *API {
return NewClusterScopedAPI(
clientSet,
nil,
l5dClientSet,
"fake",
CJ,
CM,
Deploy,
DS,
Endpoint,
Job,
MWC,
NS,
Pod,
ExtWorkload,
RC,
RS,
SP,
SS,
Svc,
Node,
ES,
Srv,
Secret,
ExtWorkload,
)
}
// NewFakeMetadataAPI provides a mock Kubernetes API for testing.
func NewFakeMetadataAPI(configs []string) (*MetadataAPI, error) {
sch := runtime.NewScheme()
metav1.AddMetaToScheme(sch)
var objs []runtime.Object
for _, config := range configs {
obj, err := k8s.ToRuntimeObject(config)
if err != nil {
return nil, err
}
objMeta, err := toPartialObjectMetadata(obj)
if err != nil {
return nil, err
}
objs = append(objs, objMeta)
}
metadataClient := fake.NewSimpleMetadataClient(sch, objs...)
return newClusterScopedMetadataAPI(
metadataClient,
"fake",
CJ,
CM,
Deploy,
DS,
Endpoint,
Job,
MWC,
NS,
Pod,
RC,
RS,
SP,
SS,
Svc,
Node,
ES,
Svc,
)
}
func toPartialObjectMetadata(obj runtime.Object) (*metav1.PartialObjectMetadata, error) {
u := &unstructured.Unstructured{}
err := clientsetscheme.Scheme.Convert(obj, u, nil)
if err != nil {
return nil, err
}
return &metav1.PartialObjectMetadata{
TypeMeta: metav1.TypeMeta{
APIVersion: u.GetAPIVersion(),
Kind: u.GetKind(),
},
ObjectMeta: metav1.ObjectMeta{
Namespace: u.GetNamespace(),
Name: u.GetName(),
Annotations: u.GetAnnotations(),
Labels: u.GetLabels(),
OwnerReferences: u.GetOwnerReferences(),
},
}, nil
}
package addr
import (
"encoding/binary"
"fmt"
"math/big"
"net"
"net/netip"
"strconv"
pb "github.com/linkerd/linkerd2-proxy-api/go/net"
l5dNetPb "github.com/linkerd/linkerd2/controller/gen/common/net"
)
// DefaultWeight is the default address weight sent by the Destination service
// to the Linkerd proxies.
const DefaultWeight = 1
// PublicAddressToString formats a Viz API TCPAddress as a string.
//
// If Ipv6, the bytes should be ordered big-endian. When formatted as a
// string, the IP address should be enclosed in square brackets followed by
// the port.
func PublicAddressToString(addr *l5dNetPb.TcpAddress) string {
strIP := PublicIPToString(addr.GetIp())
strPort := strconv.Itoa(int(addr.GetPort()))
return net.JoinHostPort(strIP, strPort)
}
// PublicIPToString formats a Viz API IPAddress as a string.
func PublicIPToString(ip *l5dNetPb.IPAddress) string {
var netIP net.IP
if ip.GetIpv6() != nil {
b := make([]byte, net.IPv6len)
binary.BigEndian.PutUint64(b[:8], ip.GetIpv6().GetFirst())
binary.BigEndian.PutUint64(b[8:], ip.GetIpv6().GetLast())
netIP = net.IP(b)
} else if ip.GetIpv4() != 0 {
netIP = decodeIPv4ToNetIP(ip.GetIpv4())
}
if netIP == nil {
return ""
}
return netIP.String()
}
// ProxyAddressToString formats a Proxy API TCPAddress as a string.
func ProxyAddressToString(addr *pb.TcpAddress) string {
vizIP := FromProxyAPI(addr.GetIp())
if vizIP == nil {
return ""
}
strIP := PublicIPToString(vizIP)
strPort := strconv.Itoa(int(addr.GetPort()))
return net.JoinHostPort(strIP, strPort)
}
// ParseProxyIP parses an IP Address string into a Proxy API IPAddress.
func ParseProxyIP(ip string) (*pb.IPAddress, error) {
addr, err := netip.ParseAddr(ip)
if err != nil {
return nil, fmt.Errorf("invalid IP address: %s", ip)
}
if addr.Is4() {
ipBytes := addr.As4()
return &pb.IPAddress{
Ip: &pb.IPAddress_Ipv4{
Ipv4: binary.BigEndian.Uint32(ipBytes[:]),
},
}, nil
} else if addr.Is6() {
ipBytes := addr.As16()
return &pb.IPAddress{
Ip: &pb.IPAddress_Ipv6{
Ipv6: &pb.IPv6{
First: binary.BigEndian.Uint64(ipBytes[:8]),
Last: binary.BigEndian.Uint64(ipBytes[8:]),
},
},
}, nil
}
return nil, fmt.Errorf("invalid IP address: %s", ip)
}
// ParsePublicIP parses an IP Address string into a Viz API IPAddress.
func ParsePublicIP(ip string) (*l5dNetPb.IPAddress, error) {
addr, err := ParseProxyIP(ip)
if err != nil {
return nil, err
}
return FromProxyAPI(addr), nil
}
// NetToPublic converts a Proxy API TCPAddress to a Viz API
// TCPAddress.
func NetToPublic(net *pb.TcpAddress) *l5dNetPb.TcpAddress {
ip := FromProxyAPI(net.GetIp())
return &l5dNetPb.TcpAddress{
Ip: ip,
Port: net.GetPort(),
}
}
// FromProxyAPI casts an IPAddress from the linkerd2-proxy-api.go.net package
// to the linkerd2.controller.gen.common.net package
func FromProxyAPI(net *pb.IPAddress) *l5dNetPb.IPAddress {
switch ip := net.GetIp().(type) {
case *pb.IPAddress_Ipv6:
return &l5dNetPb.IPAddress{
Ip: &l5dNetPb.IPAddress_Ipv6{
Ipv6: &l5dNetPb.IPv6{
First: ip.Ipv6.First,
Last: ip.Ipv6.Last,
},
},
}
case *pb.IPAddress_Ipv4:
return &l5dNetPb.IPAddress{
Ip: &l5dNetPb.IPAddress_Ipv4{
Ipv4: ip.Ipv4,
},
}
}
return nil
}
// decodeIPv4ToNetIP converts IPv4 uint32 to an IPv4 net IP.
func decodeIPv4ToNetIP(ip uint32) net.IP {
oBigInt := big.NewInt(0)
oBigInt = oBigInt.SetUint64(uint64(ip))
return IntToIPv4(oBigInt)
}
// IPToInt converts net.IP to bigInt
// It can support both IPv4 and IPv6.
func IPToInt(ip net.IP) *big.Int {
oBigInt := big.NewInt(0)
oBigInt.SetBytes(ip)
return oBigInt
}
// IntToIPv4 converts IPv4 bigInt into an IPv4 net IP.
func IntToIPv4(intip *big.Int) net.IP {
ipByte := make([]byte, net.IPv4len)
uint32IP := intip.Uint64()
binary.BigEndian.PutUint32(ipByte, uint32(uint32IP))
return net.IP(ipByte)
}
package charts
import (
"bytes"
"errors"
"net/http"
"path"
"strings"
"github.com/linkerd/linkerd2/pkg/charts/static"
"github.com/linkerd/linkerd2/pkg/version"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/engine"
"sigs.k8s.io/yaml"
)
const versionPlaceholder = "linkerdVersionValue"
var (
// L5dPartials is the list of templates in partials chart
// Keep this slice synced with the contents of /charts/partials
L5dPartials = []string{
"charts/partials/" + chartutil.ChartfileName,
"charts/partials/templates/_affinity.tpl",
"charts/partials/templates/_capabilities.tpl",
"charts/partials/templates/_debug.tpl",
"charts/partials/templates/_helpers.tpl",
"charts/partials/templates/_metadata.tpl",
"charts/partials/templates/_nodeselector.tpl",
"charts/partials/templates/_network-validator.tpl",
"charts/partials/templates/_proxy-config-ann.tpl",
"charts/partials/templates/_proxy-init.tpl",
"charts/partials/templates/_proxy.tpl",
"charts/partials/templates/_pull-secrets.tpl",
"charts/partials/templates/_resources.tpl",
"charts/partials/templates/_tolerations.tpl",
"charts/partials/templates/_trace.tpl",
"charts/partials/templates/_validate.tpl",
"charts/partials/templates/_volumes.tpl",
}
)
// Chart holds the necessary info to render a Helm chart
type Chart struct {
Name string
Dir string
Namespace string
// RawValues are yaml-formatted values entries. Either this or Values
// should be set, but not both
RawValues []byte
// Values are the config key-value entries. Either this or RawValues should
// be set, but not both
Values map[string]any
Files []*loader.BufferedFile
Fs http.FileSystem
}
func (c *Chart) render(partialsFiles []*loader.BufferedFile) (bytes.Buffer, error) {
if err := FilesReader(c.Fs, c.Dir+"/", c.Files); err != nil {
return bytes.Buffer{}, err
}
// static.Templates is used as partials are always available there
if err := FilesReader(static.Templates, "", partialsFiles); err != nil {
return bytes.Buffer{}, err
}
// Create chart and render templates
chart, err := loader.LoadFiles(append(c.Files, partialsFiles...))
if err != nil {
return bytes.Buffer{}, err
}
releaseOptions := chartutil.ReleaseOptions{
Name: c.Name,
IsInstall: true,
IsUpgrade: false,
Namespace: c.Namespace,
}
if len(c.RawValues) > 0 {
if c.Values != nil {
return bytes.Buffer{}, errors.New("either RawValues or Values should be set, but not both")
}
err = yaml.Unmarshal(c.RawValues, &c.Values)
if err != nil {
return bytes.Buffer{}, err
}
}
valuesToRender, err := chartutil.ToRenderValues(chart, c.Values, releaseOptions, nil)
if err != nil {
return bytes.Buffer{}, err
}
release, _ := valuesToRender["Release"].(map[string]interface{})
release["Service"] = "CLI"
renderedTemplates, err := engine.Render(chart, valuesToRender)
if err != nil {
return bytes.Buffer{}, err
}
// Merge templates and inject
var buf bytes.Buffer
for _, tmpl := range c.Files {
t := path.Join(releaseOptions.Name, tmpl.Name)
if _, err := buf.WriteString(renderedTemplates[t]); err != nil {
return bytes.Buffer{}, err
}
}
return buf, nil
}
// Render returns a bytes buffer with the result of rendering a Helm chart
func (c *Chart) Render() (bytes.Buffer, error) {
l5dPartials := []*loader.BufferedFile{}
for _, template := range L5dPartials {
l5dPartials = append(l5dPartials, &loader.BufferedFile{
Name: template,
})
}
return c.render(l5dPartials)
}
// RenderCNI returns a bytes buffer with the result of rendering a Helm chart
func (c *Chart) RenderCNI() (bytes.Buffer, error) {
cniPartials := []*loader.BufferedFile{
{Name: "charts/partials/" + chartutil.ChartfileName},
{Name: "charts/partials/templates/_helpers.tpl"},
{Name: "charts/partials/templates/_metadata.tpl"},
{Name: "charts/partials/templates/_pull-secrets.tpl"},
{Name: "charts/partials/templates/_tolerations.tpl"},
{Name: "charts/partials/templates/_resources.tpl"},
}
return c.render(cniPartials)
}
// ReadFile updates the buffered file with the data read from disk
func ReadFile(fs http.FileSystem, dir string, f *loader.BufferedFile) error {
filename := dir + f.Name
if dir == "" {
filename = filename[7:]
}
file, err := fs.Open(filename)
if err != nil {
return err
}
defer file.Close()
buf := new(bytes.Buffer)
if _, err := buf.ReadFrom(file); err != nil {
return err
}
f.Data = buf.Bytes()
return nil
}
// FilesReader reads all the files from a directory
func FilesReader(fs http.FileSystem, dir string, files []*loader.BufferedFile) error {
for _, f := range files {
if err := ReadFile(fs, dir, f); err != nil {
return err
}
}
return nil
}
// InsertVersion returns the chart values file contents passed in
// with the version placeholder replaced with the current version
func InsertVersion(data []byte) []byte {
dataWithVersion := strings.ReplaceAll(string(data), versionPlaceholder, version.Version)
return []byte(dataWithVersion)
}
// InsertVersionValues returns the chart values with the version placeholder
// replaced with the current version.
func InsertVersionValues(values chartutil.Values) (chartutil.Values, error) {
raw, err := values.YAML()
if err != nil {
return nil, err
}
return chartutil.ReadValues(InsertVersion([]byte(raw)))
}
// OverrideFromFile overrides the given map with the given file from FS
func OverrideFromFile(values map[string]interface{}, fs http.FileSystem, chartName, name string) (map[string]interface{}, error) {
// Load Values file
valuesOverride := loader.BufferedFile{
Name: name,
}
if err := ReadFile(fs, chartName+"/", &valuesOverride); err != nil {
return nil, err
}
var valuesOverrideMap map[string]interface{}
err := yaml.Unmarshal(valuesOverride.Data, &valuesOverrideMap)
if err != nil {
return nil, err
}
return MergeMaps(valuesOverrideMap, values), nil
}
// MergeMaps returns the resultant map after merging given two maps of type map[string]interface{}
// The inputs are not mutated and the second map i.e b's values take precedence during merge.
// This gives semantically correct merge compared with `mergo.Merge` (with boolean values).
// See https://github.com/imdario/mergo/issues/129
func MergeMaps(a, b map[string]interface{}) map[string]interface{} {
out := make(map[string]interface{}, len(a))
for k, v := range a {
out[k] = v
}
for k, v := range b {
if v, ok := v.(map[string]interface{}); ok {
if av, ok := out[k]; ok {
if av, ok := av.(map[string]interface{}); ok {
out[k] = MergeMaps(av, v)
continue
}
}
}
out[k] = v
}
return out
}
package linkerd2
import (
"errors"
"fmt"
"github.com/imdario/mergo"
"github.com/linkerd/linkerd2/pkg/charts"
"github.com/linkerd/linkerd2/pkg/charts/static"
"github.com/linkerd/linkerd2/pkg/k8s"
"github.com/linkerd/linkerd2/pkg/version"
"helm.sh/helm/v3/pkg/chart/loader"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
)
const (
// HelmChartDirCrds is the directory name for the linkerd-crds chart
HelmChartDirCrds = "linkerd-crds"
// HelmChartDirCP is the directory name for the linkerd-control-plane chart
HelmChartDirCP = "linkerd-control-plane"
)
type (
// Values contains the top-level elements in the Helm charts
Values struct {
ControllerImage string `json:"controllerImage"`
ControllerReplicas uint `json:"controllerReplicas"`
ControllerUID int64 `json:"controllerUID"`
ControllerGID int64 `json:"controllerGID"`
EnableH2Upgrade bool `json:"enableH2Upgrade"`
EnablePodAntiAffinity bool `json:"enablePodAntiAffinity"`
NodeAffinity map[string]interface{} `json:"nodeAffinity"`
EnablePodDisruptionBudget bool `json:"enablePodDisruptionBudget"`
Controller *Controller `json:"controller"`
WebhookFailurePolicy string `json:"webhookFailurePolicy"`
DeploymentStrategy map[string]interface{} `json:"deploymentStrategy,omitempty"`
DisableHeartBeat bool `json:"disableHeartBeat"`
HeartbeatSchedule string `json:"heartbeatSchedule"`
Configs ConfigJSONs `json:"configs"`
ClusterDomain string `json:"clusterDomain"`
ClusterNetworks string `json:"clusterNetworks"`
ImagePullPolicy string `json:"imagePullPolicy"`
CliVersion string `json:"cliVersion"`
ControllerLogLevel string `json:"controllerLogLevel"`
ControllerLogFormat string `json:"controllerLogFormat"`
ProxyContainerName string `json:"proxyContainerName"`
HighAvailability bool `json:"highAvailability"`
CNIEnabled bool `json:"cniEnabled"`
EnableEndpointSlices bool `json:"enableEndpointSlices"`
DisableIPv6 bool `json:"disableIPv6"`
ControlPlaneTracing bool `json:"controlPlaneTracing"`
ControlPlaneTracingNamespace string `json:"controlPlaneTracingNamespace"`
IdentityTrustAnchorsPEM string `json:"identityTrustAnchorsPEM"`
IdentityTrustDomain string `json:"identityTrustDomain"`
PrometheusURL string `json:"prometheusUrl"`
ImagePullSecrets []map[string]string `json:"imagePullSecrets"`
LinkerdVersion string `json:"linkerdVersion"`
RevisionHistoryLimit uint `json:"revisionHistoryLimit"`
DestinationController map[string]interface{} `json:"destinationController"`
Heartbeat map[string]interface{} `json:"heartbeat"`
SPValidator map[string]interface{} `json:"spValidator"`
PodAnnotations map[string]string `json:"podAnnotations"`
PodLabels map[string]string `json:"podLabels"`
PriorityClassName string `json:"priorityClassName"`
PodMonitor *PodMonitor `json:"podMonitor"`
PolicyController *PolicyController `json:"policyController"`
Proxy *Proxy `json:"proxy"`
ProxyInit *ProxyInit `json:"proxyInit"`
NetworkValidator *NetworkValidator `json:"networkValidator"`
Identity *Identity `json:"identity"`
DebugContainer *DebugContainer `json:"debugContainer"`
ProxyInjector *ProxyInjector `json:"proxyInjector"`
ProfileValidator *Webhook `json:"profileValidator"`
PolicyValidator *Webhook `json:"policyValidator"`
NodeSelector map[string]string `json:"nodeSelector"`
Tolerations []interface{} `json:"tolerations"`
DestinationResources *Resources `json:"destinationResources"`
HeartbeatResources *Resources `json:"heartbeatResources"`
IdentityResources *Resources `json:"identityResources"`
ProxyInjectorResources *Resources `json:"proxyInjectorResources"`
DestinationProxyResources *Resources `json:"destinationProxyResources"`
IdentityProxyResources *Resources `json:"identityProxyResources"`
ProxyInjectorProxyResources *Resources `json:"proxyInjectorProxyResources"`
}
// Controller contains the fields to set the controller container
Controller struct {
PodDisruptionBudget *PodDisruptionBudget `json:"podDisruptionBudget"`
}
// PodDisruptionBudget contains the fields to set the PDB
PodDisruptionBudget struct {
MaxUnavailable int `json:"maxUnavailable"`
}
// ConfigJSONs is the JSON encoding of the Linkerd configuration
ConfigJSONs struct {
Global string `json:"global"`
Proxy string `json:"proxy"`
Install string `json:"install"`
}
// Proxy contains the fields to set the proxy sidecar container
Proxy struct {
Capabilities *Capabilities `json:"capabilities"`
// This should match .Resources.CPU.Limit, but must be a whole number
Cores int64 `json:"cores,omitempty"`
EnableExternalProfiles bool `json:"enableExternalProfiles"`
Image *Image `json:"image"`
EnableShutdownEndpoint bool `json:"enableShutdownEndpoint"`
LogLevel string `json:"logLevel"`
LogFormat string `json:"logFormat"`
LogHTTPHeaders string `json:"logHTTPHeaders"`
SAMountPath *VolumeMountPath `json:"saMountPath"`
Ports *Ports `json:"ports"`
Resources *Resources `json:"resources"`
UID int64 `json:"uid"`
GID int64 `json:"gid"`
WaitBeforeExitSeconds uint64 `json:"waitBeforeExitSeconds"`
IsGateway bool `json:"isGateway"`
IsIngress bool `json:"isIngress"`
RequireIdentityOnInboundPorts string `json:"requireIdentityOnInboundPorts"`
OutboundConnectTimeout string `json:"outboundConnectTimeout"`
InboundConnectTimeout string `json:"inboundConnectTimeout"`
OutboundDiscoveryCacheUnusedTimeout string `json:"outboundDiscoveryCacheUnusedTimeout"`
InboundDiscoveryCacheUnusedTimeout string `json:"inboundDiscoveryCacheUnusedTimeout"`
DisableOutboundProtocolDetectTimeout bool `json:"disableOutboundProtocolDetectTimeout"`
DisableInboundProtocolDetectTimeout bool `json:"disableInboundProtocolDetectTimeout"`
PodInboundPorts string `json:"podInboundPorts"`
OpaquePorts string `json:"opaquePorts"`
Await bool `json:"await"`
DefaultInboundPolicy string `json:"defaultInboundPolicy"`
AccessLog string `json:"accessLog"`
ShutdownGracePeriod string `json:"shutdownGracePeriod"`
NativeSidecar bool `json:"nativeSidecar"`
StartupProbe *StartupProbe `json:"startupProbe"`
ReadinessProbe *Probe `json:"readinessProbe"`
LivenessProbe *Probe `json:"livenessProbe"`
Control *ProxyControl `json:"control"`
AdditionalEnv []corev1.EnvVar `json:"additionalEnv"`
ExperimentalEnv []corev1.EnvVar `json:"experimentalEnv"`
Inbound ProxyParams `json:"inbound,omitempty"`
Outbound ProxyParams `json:"outbound,omitempty"`
}
ProxyParams = map[string]ProxyScopeParams
ProxyScopeParams = map[string]ProxyProtoParams
ProxyProtoParams = map[string]interface{}
ProxyControl struct {
Streams *ProxyControlStreams `json:"streams"`
}
ProxyControlStreams struct {
InitialTimeout string `json:"initialTimeout"`
IdleTimeout string `json:"idleTimeout"`
Lifetime string `json:"lifetime"`
}
// ProxyInit contains the fields to set the proxy-init container
ProxyInit struct {
Capabilities *Capabilities `json:"capabilities"`
IgnoreInboundPorts string `json:"ignoreInboundPorts"`
IgnoreOutboundPorts string `json:"ignoreOutboundPorts"`
KubeAPIServerPorts string `json:"kubeAPIServerPorts"`
SkipSubnets string `json:"skipSubnets"`
LogLevel string `json:"logLevel"`
LogFormat string `json:"logFormat"`
Image *Image `json:"image"`
SAMountPath *VolumeMountPath `json:"saMountPath"`
XTMountPath *VolumeMountPath `json:"xtMountPath"`
/* DEPRECATED: should be removed after stable-2.16.0, left in for bc */
Resources *Resources `json:"resources"`
CloseWaitTimeoutSecs int64 `json:"closeWaitTimeoutSecs"`
Privileged bool `json:"privileged"`
RunAsRoot bool `json:"runAsRoot"`
RunAsUser int64 `json:"runAsUser"`
RunAsGroup int64 `json:"runAsGroup"`
IptablesMode string `json:"iptablesMode"`
}
NetworkValidator struct {
LogLevel string `json:"logLevel"`
LogFormat string `json:"logFormat"`
ConnectAddr string `json:"connectAddr"`
ListenAddr string `json:"listenAddr"`
Timeout string `json:"timeout"`
EnableSecurityContext bool `json:"enableSecurityContext"`
}
// DebugContainer contains the fields to set the debugging sidecar
DebugContainer struct {
Image *Image `json:"image"`
}
// PodMonitor contains the fields to configure the Prometheus Operator `PodMonitor`
PodMonitor struct {
Enabled bool `json:"enabled"`
ScrapeInterval string `json:"scrapeInterval"`
ScrapeTimeout string `json:"scrapeTimeout"`
Controller *PodMonitorController `json:"controller"`
ServiceMirror *PodMonitorComponent `json:"serviceMirror"`
Proxy *PodMonitorComponent `json:"proxy"`
}
// PodMonitorController contains the fields to configure the Prometheus Operator `PodMonitor` for the control-plane
PodMonitorController struct {
Enabled bool `json:"enabled"`
NamespaceSelector string `json:"namespaceSelector"`
}
// PodMonitorComponent contains the fields to configure the Prometheus Operator `PodMonitor` for other components
PodMonitorComponent struct {
Enabled bool `json:"enabled"`
}
// PolicyController contains the fields to configure the policy controller container
PolicyController struct {
Image *Image `json:"image"`
Resources *Resources `json:"resources"`
LogLevel string `json:"logLevel"`
ProbeNetworks []string `json:"probeNetworks"`
}
// Image contains the details to define a container image
Image struct {
Name string `json:"name"`
PullPolicy string `json:"pullPolicy"`
Version string `json:"version"`
}
// Ports contains all the port-related setups
Ports struct {
Admin int32 `json:"admin"`
Control int32 `json:"control"`
Inbound int32 `json:"inbound"`
Outbound int32 `json:"outbound"`
}
Probe struct {
InitialDelaySeconds uint `json:"initialDelaySeconds"`
TimeoutSeconds uint `json:"timeoutSeconds"`
}
// Constraints wraps the Limit and Request settings for computational resources
Constraints struct {
Limit string `json:"limit"`
Request string `json:"request"`
}
// Capabilities contains the SecurityContext capabilities to add/drop into the injected
// containers
Capabilities struct {
Add []string `json:"add"`
Drop []string `json:"drop"`
}
// VolumeMountPath contains the details for volume mounts
VolumeMountPath struct {
Name string `json:"name"`
MountPath string `json:"mountPath"`
ReadOnly bool `json:"readOnly"`
}
// Resources represents the computational resources setup for a given container
Resources struct {
CPU Constraints `json:"cpu"`
Memory Constraints `json:"memory"`
EphemeralStorage Constraints `json:"ephemeral-storage"`
}
// StartupProbe represents the initContainer startup probe parameters for the proxy
StartupProbe struct {
InitialDelaySeconds uint `json:"initialDelaySeconds"`
PeriodSeconds uint `json:"periodSeconds"`
FailureThreshold uint `json:"failureThreshold"`
}
// Identity contains the fields to set the identity variables in the proxy
// sidecar container
Identity struct {
ExternalCA bool `json:"externalCA"`
ServiceAccountTokenProjection bool `json:"serviceAccountTokenProjection"`
Issuer *Issuer `json:"issuer"`
KubeAPI *KubeAPI `json:"kubeAPI"`
AdditionalEnv []corev1.EnvVar `json:"additionalEnv"`
ExperimentalEnv []corev1.EnvVar `json:"experimentalEnv"`
}
// Issuer has the Helm variables of the identity issuer
Issuer struct {
Scheme string `json:"scheme"`
ClockSkewAllowance string `json:"clockSkewAllowance"`
IssuanceLifetime string `json:"issuanceLifetime"`
TLS *IssuerTLS `json:"tls"`
}
// KubeAPI contains the kube-apiserver client config
KubeAPI struct {
ClientQPS float32 `json:"clientQPS"`
ClientBurst int `json:"clientBurst"`
}
// ProxyInjector configures the proxy-injector webhook
ProxyInjector struct {
Webhook
AdditionalEnv []corev1.EnvVar `json:"additionalEnv"`
ExperimentalEnv []corev1.EnvVar `json:"experimentalEnv"`
}
// Webhook Helm variables for a webhook
Webhook struct {
*TLS
NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector"`
}
// TLS has a pair of PEM-encoded key and certificate variables used in the
// Helm templates
TLS struct {
ExternalSecret bool `json:"externalSecret"`
KeyPEM string `json:"keyPEM"`
CrtPEM string `json:"crtPEM"`
CaBundle string `json:"caBundle"`
InjectCaFrom string `json:"injectCaFrom"`
InjectCaFromSecret string `json:"injectCaFromSecret"`
}
// IssuerTLS is a stripped down version of TLS that lacks the integral caBundle.
// It is tracked separately in the field 'IdentityTrustAnchorsPEM'
IssuerTLS struct {
KeyPEM string `json:"keyPEM"`
CrtPEM string `json:"crtPEM"`
}
)
// NewValues returns a new instance of the Values type.
func NewValues() (*Values, error) {
v, err := readDefaults(HelmChartDirCrds + "/values.yaml")
if err != nil {
return nil, err
}
vCP, err := readDefaults(HelmChartDirCP + "/values.yaml")
if err != nil {
return nil, err
}
*v, err = v.Merge(*vCP)
if err != nil {
return nil, err
}
v.DebugContainer.Image.Version = version.Version
v.CliVersion = k8s.CreatedByAnnotationValue()
v.ProfileValidator.TLS = &TLS{}
v.ProxyInjector.TLS = &TLS{}
v.ProxyContainerName = k8s.ProxyContainerName
return v, nil
}
// ValuesFromConfigMap converts the data in linkerd-config into
// a Values struct
func ValuesFromConfigMap(cm *corev1.ConfigMap) (*Values, error) {
raw, ok := cm.Data["values"]
if !ok {
return nil, errors.New("Linkerd values not found in ConfigMap")
}
v := &Values{}
err := yaml.Unmarshal([]byte(raw), &v)
return v, err
}
// MergeHAValues retrieves the default HA values and merges them into the received values
func MergeHAValues(values *Values) error {
haValues, err := readDefaults(HelmChartDirCP + "/values-ha.yaml")
if err != nil {
return err
}
*values, err = values.Merge(*haValues)
return err
}
// readDefaults read all the default variables from filename.
func readDefaults(filename string) (*Values, error) {
valuesFile := &loader.BufferedFile{Name: filename}
if err := charts.ReadFile(static.Templates, "/", valuesFile); err != nil {
return nil, err
}
var values Values
err := yaml.Unmarshal(charts.InsertVersion(valuesFile.Data), &values)
return &values, err
}
// Merge merges the non-empty properties of src into v.
// A new Values instance is returned. Neither src nor v are mutated after
// calling Merge.
func (v Values) Merge(src Values) (Values, error) {
// By default, mergo.Merge doesn't overwrite any existing non-empty values
// in its first argument. So in HA mode, we are merging values.yaml into
// values-ha.yaml, instead of the other way round (like Helm). This ensures
// that all the HA values take precedence.
if err := mergo.Merge(&src, v); err != nil {
return Values{}, err
}
return src, nil
}
// ToMap converts the Values intro a map[string]interface{}
func (v *Values) ToMap() (map[string]interface{}, error) {
var valuesMap map[string]interface{}
rawValues, err := yaml.Marshal(v)
if err != nil {
return nil, fmt.Errorf("Failed to marshal the values struct: %w", err)
}
err = yaml.Unmarshal(rawValues, &valuesMap)
if err != nil {
return nil, fmt.Errorf("Failed to Unmarshal Values into a map: %w", err)
}
return valuesMap, nil
}
// DeepCopy creates a deep copy of the Values struct by marshalling to yaml and
// then unmarshalling a new struct.
func (v *Values) DeepCopy() (*Values, error) {
dst := Values{}
bytes, err := yaml.Marshal(v)
if err != nil {
return nil, err
}
err = yaml.Unmarshal(bytes, &dst)
if err != nil {
return nil, err
}
return &dst, nil
}
func (v *Values) String() string {
bytes, _ := yaml.Marshal(v)
return string(bytes)
}
package static
import (
"path"
"path/filepath"
"runtime"
)
// GetRepoRoot returns the full path to the root of the repo. We assume this
// function is only called from the `Templates` var above, and that this source
// file lives at `pkg/charts/static`, relative to the root of the repo.
func GetRepoRoot() string {
// /foo/bar/linkerd2/pkg/charts/static/templates.go
_, filename, _, _ := runtime.Caller(0)
// /foo/bar/linkerd2/pkg/charts/static
dir := filepath.Dir(filename)
// filepath.Dir returns the parent directory, so that combined with joining
// ".." walks 3 levels up the tree:
// /foo/bar/linkerd2
return filepath.Dir(path.Join(dir, "../.."))
}
package cmd
import (
"bufio"
"bytes"
"context"
"errors"
"fmt"
"io"
"os"
"strings"
"github.com/linkerd/linkerd2/pkg/k8s"
"github.com/linkerd/linkerd2/pkg/k8s/resource"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/selection"
yamlDecoder "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/tools/clientcmd"
"sigs.k8s.io/yaml"
)
var (
// DefaultDockerRegistry specifies the default location for Linkerd's images.
DefaultDockerRegistry = "cr.l5d.io/linkerd"
)
const (
JsonOutput = "json"
YamlOutput = "yaml"
)
// GetDefaultNamespace fetches the default namespace
// used in the current KubeConfig context
func GetDefaultNamespace(kubeconfigPath, kubeContext string) string {
rules := clientcmd.NewDefaultClientConfigLoadingRules()
if kubeconfigPath != "" {
rules.ExplicitPath = kubeconfigPath
}
overrides := &clientcmd.ConfigOverrides{CurrentContext: kubeContext}
kubeCfg := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, overrides)
ns, _, err := kubeCfg.Namespace()
if err != nil {
log.Warnf(`could not set namespace from kubectl context, using 'default' namespace: %s
ensure the KUBECONFIG path %s is valid`, err, kubeconfigPath)
return corev1.NamespaceDefault
}
return ns
}
// Uninstall prints all cluster-scoped resources matching the given selector
// for the purposes of deleting them.
func Uninstall(ctx context.Context, k8sAPI *k8s.KubernetesAPI, selector string, format string) error {
resources, err := resource.FetchKubernetesResources(ctx, k8sAPI,
metav1.ListOptions{LabelSelector: selector},
)
if err != nil {
return err
}
if len(resources) == 0 {
return errors.New("No resources found to uninstall")
}
for _, r := range resources {
if format == YamlOutput {
if err := r.RenderResource(os.Stdout); err != nil {
return fmt.Errorf("error rendering Kubernetes resource: %w", err)
}
} else if format == JsonOutput {
if err := r.RenderResourceJSON(os.Stdout); err != nil {
return fmt.Errorf("error rendering Kubernetes resource: %w", err)
}
} else {
return fmt.Errorf("unsupported format %s", format)
}
}
return nil
}
// Prune takes an install manifest and prints all resources on the cluster which
// match the given label selector but are not in the given manifest. Users are
// expected to pipe these resources to `kubectl delete` to clean up resources
// left on the cluster which are no longer part of the install manifest.
func Prune(ctx context.Context, k8sAPI *k8s.KubernetesAPI, expectedManifests string, selector string, format string) error {
expectedResources := []resource.Kubernetes{}
reader := yamlDecoder.NewYAMLReader(bufio.NewReaderSize(strings.NewReader(expectedManifests), 4096))
for {
manifest, err := reader.Read()
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return err
}
resource := resource.Kubernetes{}
err = yaml.Unmarshal(manifest, &resource)
if err != nil {
fmt.Fprintf(os.Stderr, "error parsing manifest: %s", manifest)
os.Exit(1)
}
expectedResources = append(expectedResources, resource)
}
listOptions := metav1.ListOptions{
LabelSelector: selector,
}
resources, err := resource.FetchPrunableResources(ctx, k8sAPI, metav1.NamespaceAll, listOptions)
if err != nil {
fmt.Fprintf(os.Stderr, "error fetching resources: %s\n", err)
os.Exit(1)
}
for _, resource := range resources {
// If the resource is not in the expected resource list, render it for
// pruning.
if !resourceListContains(expectedResources, resource) {
if format == YamlOutput {
err = resource.RenderResource(os.Stdout)
} else if format == JsonOutput {
err = resource.RenderResourceJSON(os.Stdout)
} else {
return fmt.Errorf("unsupported format %s", format)
}
if err != nil {
return fmt.Errorf("error rendering Kubernetes resource: %w\n", err)
}
}
}
return nil
}
func resourceListContains(list []resource.Kubernetes, a resource.Kubernetes) bool {
for _, r := range list {
if resourceEquals(a, r) {
return true
}
}
return false
}
func resourceEquals(a resource.Kubernetes, b resource.Kubernetes) bool {
return a.GroupVersionKind().GroupKind() == b.GroupVersionKind().GroupKind() &&
a.GetName() == b.GetName() &&
a.GetNamespace() == b.GetNamespace()
}
// ConfigureNamespaceFlagCompletion sets up resource-aware completion for command
// flags that accept a namespace name
func ConfigureNamespaceFlagCompletion(
cmd *cobra.Command,
flagNames []string,
kubeconfigPath string,
impersonate string,
impersonateGroup []string,
kubeContext string,
) {
for _, flagName := range flagNames {
cmd.RegisterFlagCompletionFunc(flagName,
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
k8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
cc := k8s.NewCommandCompletion(k8sAPI, "")
results, err := cc.Complete([]string{k8s.Namespace}, toComplete)
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
return results, cobra.ShellCompDirectiveDefault
})
}
}
// ConfigureOutputFlagCompletion sets up resource-aware completion for command
// flags that accept an output name.
func ConfigureOutputFlagCompletion(cmd *cobra.Command) {
cmd.RegisterFlagCompletionFunc("output",
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return []string{"basic", "json", "short", "table"}, cobra.ShellCompDirectiveDefault
})
}
// ConfigureKubeContextFlagCompletion sets up resource-aware completion for command
// flags based off of a kubeconfig
func ConfigureKubeContextFlagCompletion(cmd *cobra.Command, kubeconfigPath string) {
cmd.RegisterFlagCompletionFunc("context",
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
rules := clientcmd.NewDefaultClientConfigLoadingRules()
rules.ExplicitPath = kubeconfigPath
loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, &clientcmd.ConfigOverrides{})
config, err := loader.RawConfig()
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
suggestions := []string{}
uniqContexts := map[string]struct{}{}
for ctxName := range config.Contexts {
if strings.HasPrefix(ctxName, toComplete) {
if _, ok := uniqContexts[ctxName]; !ok {
suggestions = append(suggestions, ctxName)
uniqContexts[ctxName] = struct{}{}
}
}
}
return suggestions, cobra.ShellCompDirectiveDefault
})
}
// GetLabelSelector creates a label selector as a string based on a label key
// whose value may be in the set provided as an argument to the function. If the
// value set is empty then the selector will match resources where the label key
// exists regardless of value.
func GetLabelSelector(labelKey string, labelValues ...string) (string, error) {
selectionOp := selection.In
if len(labelValues) < 1 {
selectionOp = selection.Exists
}
labelRequirement, err := labels.NewRequirement(labelKey, selectionOp, labelValues)
if err != nil {
return "", err
}
selector := labels.NewSelector().Add(*labelRequirement)
return selector.String(), nil
}
// RegistryOverride replaces the registry-portion of the provided image with the provided registry.
func RegistryOverride(image, newRegistry string) string {
if image == "" {
return image
}
registry := newRegistry
if registry != "" && !strings.HasSuffix(registry, "/") {
registry += "/"
}
imageName := image
if strings.Contains(image, "/") {
imageName = image[strings.LastIndex(image, "/")+1:]
}
return registry + imageName
}
// Given a buffer containing one or more YAML documents separated by `---`,
// render each document to the writer in the specified format: json or yaml.
// Json documents are separated by a newline character.
func RenderYAMLAs(buf *bytes.Buffer, writer io.Writer, format string) error {
if format == JsonOutput {
reader := yamlDecoder.NewYAMLReader(bufio.NewReaderSize(buf, 4096))
for {
manifest, err := reader.Read()
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return err
}
bytes, err := yaml.YAMLToJSON(manifest)
if err != nil {
return err
}
_, err = writer.Write(append(bytes, '\n'))
if err != nil {
return err
}
}
return nil
}
if format == YamlOutput {
_, err := writer.Write(buf.Bytes())
return err
}
return fmt.Errorf("unsupported format %s", format)
}
package config
import (
"context"
"fmt"
"os"
"path/filepath"
"github.com/linkerd/linkerd2/pkg/k8s"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
l5dcharts "github.com/linkerd/linkerd2/pkg/charts/linkerd2"
log "github.com/sirupsen/logrus"
"sigs.k8s.io/yaml"
)
// Values returns the Value struct from the linkerd-config ConfigMap
func Values(path string) (*l5dcharts.Values, error) {
p := filepath.Clean(path)
configYaml, err := os.ReadFile(p)
if err != nil {
return nil, fmt.Errorf("failed to read config file: %w", err)
}
log.Debugf("%s config YAML: %s", p, configYaml)
values := &l5dcharts.Values{}
if err = yaml.Unmarshal(configYaml, values); err != nil {
return nil, fmt.Errorf("failed to unmarshal JSON from: %s: %w", p, err)
}
return values, err
}
// RemoveGlobalFieldIfPresent removes the `global` node and
// attaches the children nodes there.
func RemoveGlobalFieldIfPresent(bytes []byte) ([]byte, error) {
// Check if Globals is present and remove that node if it has
var valuesMap map[string]interface{}
err := yaml.Unmarshal(bytes, &valuesMap)
if err != nil {
return nil, err
}
if globalValues, ok := valuesMap["global"]; ok {
// attach those values
// Check if its a map
if val, ok := globalValues.(map[string]interface{}); ok {
for k, v := range val {
valuesMap[k] = v
}
}
// Remove global now
delete(valuesMap, "global")
}
bytes, err = yaml.Marshal(valuesMap)
if err != nil {
return nil, err
}
return bytes, nil
}
// FetchLinkerdConfigMap retrieves the `linkerd-config` ConfigMap from
// Kubernetes.
func FetchLinkerdConfigMap(ctx context.Context, k kubernetes.Interface, controlPlaneNamespace string) (*corev1.ConfigMap, error) {
cm, err := k.CoreV1().ConfigMaps(controlPlaneNamespace).Get(ctx, k8s.ConfigConfigMapName, metav1.GetOptions{})
if err != nil {
return nil, err
}
return cm, nil
}
package healthcheck
import (
"bufio"
"context"
"crypto/x509"
"errors"
"fmt"
"io"
"net"
"os"
"sort"
"strconv"
"strings"
"time"
controllerK8s "github.com/linkerd/linkerd2/controller/k8s"
l5dcharts "github.com/linkerd/linkerd2/pkg/charts/linkerd2"
"github.com/linkerd/linkerd2/pkg/config"
"github.com/linkerd/linkerd2/pkg/identity"
"github.com/linkerd/linkerd2/pkg/issuercerts"
"github.com/linkerd/linkerd2/pkg/k8s"
"github.com/linkerd/linkerd2/pkg/tls"
"github.com/linkerd/linkerd2/pkg/util"
"github.com/linkerd/linkerd2/pkg/version"
log "github.com/sirupsen/logrus"
admissionRegistration "k8s.io/api/admissionregistration/v1"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
yamlDecoder "k8s.io/apimachinery/pkg/util/yaml"
k8sVersion "k8s.io/apimachinery/pkg/version"
"k8s.io/client-go/kubernetes"
apiregistrationv1client "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1"
"sigs.k8s.io/yaml"
)
// CategoryID is an identifier for the types of health checks.
type CategoryID string
const (
// KubernetesAPIChecks adds a series of checks to validate that the caller is
// configured to interact with a working Kubernetes cluster.
KubernetesAPIChecks CategoryID = "kubernetes-api"
// KubernetesVersionChecks validate that the cluster meets the minimum version
// requirements.
KubernetesVersionChecks CategoryID = "kubernetes-version"
// LinkerdPreInstall* checks enabled by `linkerd check --pre`
// LinkerdPreInstallChecks adds checks to validate that the control plane
// namespace does not already exist, and that the user can create cluster-wide
// resources, including ClusterRole, ClusterRoleBinding, and
// CustomResourceDefinition, as well as namespace-wide resources, including
// Service, Deployment, and ConfigMap. This check only runs as part of the set
// of pre-install checks.
// This check is dependent on the output of KubernetesAPIChecks, so those
// checks must be added first.
LinkerdPreInstallChecks CategoryID = "pre-kubernetes-setup"
// LinkerdCRDChecks adds checks to validate that the control plane CRDs
// exist. These checks can be run after installing the control plane CRDs
// but before installing the control plane itself.
LinkerdCRDChecks CategoryID = "linkerd-crd"
// LinkerdConfigChecks enabled by `linkerd check config`
// LinkerdConfigChecks adds a series of checks to validate that the Linkerd
// namespace, RBAC, ServiceAccounts, and CRDs were successfully created.
// These checks specifically validate that the `linkerd install config`
// command succeeded in a multi-stage install, but also applies to a default
// `linkerd install`.
// These checks are dependent on the output of KubernetesAPIChecks, so those
// checks must be added first.
LinkerdConfigChecks CategoryID = "linkerd-config"
// LinkerdIdentity Checks the integrity of the mTLS certificates
// that the control plane is configured with
LinkerdIdentity CategoryID = "linkerd-identity"
// LinkerdWebhooksAndAPISvcTLS the integrity of the mTLS certificates
// that of the for the injector and sp webhooks and the tap api svc
LinkerdWebhooksAndAPISvcTLS CategoryID = "linkerd-webhooks-and-apisvc-tls"
// LinkerdIdentityDataPlane checks that integrity of the mTLS
// certificates that the proxies are configured with and tries to
// report useful information with respect to whether the configuration
// is compatible with the one of the control plane
LinkerdIdentityDataPlane CategoryID = "linkerd-identity-data-plane"
// LinkerdControlPlaneExistenceChecks adds a series of checks to validate that
// the control plane namespace and controller pod exist.
// These checks are dependent on the output of KubernetesAPIChecks, so those
// checks must be added first.
LinkerdControlPlaneExistenceChecks CategoryID = "linkerd-existence"
// LinkerdVersionChecks adds a series of checks to query for the latest
// version, and validate the CLI is up to date.
LinkerdVersionChecks CategoryID = "linkerd-version"
// LinkerdControlPlaneVersionChecks adds a series of checks to validate that
// the control plane is running the latest available version.
// These checks are dependent on the following:
// 1) `latestVersions` from LinkerdVersionChecks
// 2) `serverVersion` from `LinkerdControlPlaneExistenceChecks`
LinkerdControlPlaneVersionChecks CategoryID = "control-plane-version"
// LinkerdDataPlaneChecks adds data plane checks to validate that the
// data plane namespace exists, and that the proxy containers are in a
// ready state and running the latest available version. These checks
// are dependent on the output of KubernetesAPIChecks and
// `latestVersions` from LinkerdVersionChecks, so those checks must be
// added first.
LinkerdDataPlaneChecks CategoryID = "linkerd-data-plane"
// LinkerdControlPlaneProxyChecks adds data plane checks to validate the
// control-plane proxies. The checkers include running and version checks
LinkerdControlPlaneProxyChecks CategoryID = "linkerd-control-plane-proxy"
// LinkerdHAChecks adds checks to validate that the HA configuration
// is correct. These checks are no ops if linkerd is not in HA mode
LinkerdHAChecks CategoryID = "linkerd-ha-checks"
// LinkerdCNIPluginChecks adds checks to validate that the CNI
/// plugin is installed and ready
LinkerdCNIPluginChecks CategoryID = "linkerd-cni-plugin"
// LinkerdOpaquePortsDefinitionChecks adds checks to validate that the
// "opaque ports" annotation has been defined both in the service and the
// corresponding pods
LinkerdOpaquePortsDefinitionChecks CategoryID = "linkerd-opaque-ports-definition"
// LinkerdExtensionChecks adds checks to validate configuration for all
// extensions discovered in the cluster at runtime
LinkerdExtensionChecks CategoryID = "linkerd-extension-checks"
// LinkerdCNIResourceLabel is the label key that is used to identify
// whether a Kubernetes resource is related to the install-cni command
// The value is expected to be "true", "false" or "", where "false" and
// "" are equal, making "false" the default
LinkerdCNIResourceLabel = "linkerd.io/cni-resource"
linkerdCNIDisabledSkipReason = "skipping check because CNI is not enabled"
linkerdCNIResourceName = "linkerd-cni"
linkerdCNIConfigMapName = "linkerd-cni-config"
podCIDRUnavailableSkipReason = "skipping check because the nodes aren't exposing podCIDR"
configMapDoesNotExistSkipReason = "skipping check because ConigMap does not exist"
proxyInjectorOldTLSSecretName = "linkerd-proxy-injector-tls"
proxyInjectorTLSSecretName = "linkerd-proxy-injector-k8s-tls"
spValidatorOldTLSSecretName = "linkerd-sp-validator-tls"
spValidatorTLSSecretName = "linkerd-sp-validator-k8s-tls"
policyValidatorTLSSecretName = "linkerd-policy-validator-k8s-tls"
certOldKeyName = "crt.pem"
certKeyName = "tls.crt"
keyOldKeyName = "key.pem"
keyKeyName = "tls.key"
)
// AllowedClockSkew sets the allowed skew in clock synchronization
// between the system running inject command and the node(s), being
// based on assumed node's heartbeat interval (5 minutes) plus default TLS
// clock skew allowance.
//
// TODO: Make this default value overridable, e.g. by CLI flag
const AllowedClockSkew = 5*time.Minute + tls.DefaultClockSkewAllowance
var linkerdHAControlPlaneComponents = []string{
"linkerd-destination",
"linkerd-identity",
"linkerd-proxy-injector",
}
// ExpectedServiceAccountNames is a list of the service accounts that a healthy
// Linkerd installation should have. Note that linkerd-heartbeat is optional,
// so it doesn't appear here.
var ExpectedServiceAccountNames = []string{
"linkerd-destination",
"linkerd-identity",
"linkerd-proxy-injector",
}
var (
retryWindow = 5 * time.Second
// RequestTimeout is the time it takes for a request to timeout
RequestTimeout = 30 * time.Second
)
// Resource provides a way to describe a Kubernetes object, kind, and name.
// TODO: Consider sharing with the inject package's ResourceConfig.workload
// struct, as it wraps both runtime.Object and metav1.TypeMeta.
type Resource struct {
groupVersionKind schema.GroupVersionKind
name string
}
// String outputs the resource in kind.group/name format, intended for
// `linkerd install`.
func (r *Resource) String() string {
return fmt.Sprintf("%s/%s", strings.ToLower(r.groupVersionKind.GroupKind().String()), r.name)
}
// ResourceError provides a custom error type for resource existence checks,
// useful in printing detailed error messages in `linkerd check` and
// `linkerd install`.
type ResourceError struct {
resourceName string
Resources []Resource
}
// Error satisfies the error interface for ResourceError. The output is intended
// for `linkerd check`.
func (e ResourceError) Error() string {
names := []string{}
for _, res := range e.Resources {
names = append(names, res.name)
}
return fmt.Sprintf("%s found but should not exist: %s", e.resourceName, strings.Join(names, " "))
}
// CategoryError provides a custom error type that also contains check category that emitted the error,
// useful when needed to distinguish between errors from multiple categories
type CategoryError struct {
Category CategoryID
Err error
}
// Error satisfies the error interface for CategoryError.
func (e CategoryError) Error() string {
return e.Err.Error()
}
// IsCategoryError returns true if passed in error is of type CategoryError and belong to the given category
func IsCategoryError(err error, categoryID CategoryID) bool {
var ce CategoryError
if errors.As(err, &ce) {
return ce.Category == categoryID
}
return false
}
// SkipError is returned by a check in case this check needs to be ignored.
type SkipError struct {
Reason string
}
// Error satisfies the error interface for SkipError.
func (e SkipError) Error() string {
return e.Reason
}
// VerboseSuccess implements the error interface but represents a success with
// a message.
type VerboseSuccess struct {
Message string
}
// Error satisfies the error interface for VerboseSuccess. Since VerboseSuccess
// does not actually represent a failure, this returns the empty string.
func (e VerboseSuccess) Error() string {
return ""
}
// Checker is a smallest unit performing a single check
type Checker struct {
// description is the short description that's printed to the command line
// when the check is executed
description string
// hintAnchor, when appended to `HintBaseURL`, provides a URL to more
// information about the check
hintAnchor string
// fatal indicates that all remaining checks should be aborted if this check
// fails; it should only be used if subsequent checks cannot possibly succeed
// (default false)
fatal bool
// warning indicates that if this check fails, it should be reported, but it
// should not impact the overall outcome of the health check (default false)
warning bool
// retryDeadline establishes a deadline before which this check should be
// retried; if the deadline has passed, the check fails (default: no retries)
retryDeadline time.Time
// surfaceErrorOnRetry indicates that the error message should be displayed
// even if the check will be retried. This is useful if the error message
// contains the current status of the check.
surfaceErrorOnRetry bool
// check is the function that's called to execute the check; if the function
// returns an error, the check fails
check func(context.Context) error
}
// NewChecker returns a new instance of checker type
func NewChecker(description string) *Checker {
return &Checker{
description: description,
retryDeadline: time.Time{},
}
}
// WithHintAnchor returns a checker with the given hint anchor
func (c *Checker) WithHintAnchor(hint string) *Checker {
c.hintAnchor = hint
return c
}
// Fatal returns a checker with the fatal field set
func (c *Checker) Fatal() *Checker {
c.fatal = true
return c
}
// Warning returns a checker with the warning field set
func (c *Checker) Warning() *Checker {
c.warning = true
return c
}
// WithRetryDeadline returns a checker with the provided retry timeout
func (c *Checker) WithRetryDeadline(retryDeadLine time.Time) *Checker {
c.retryDeadline = retryDeadLine
return c
}
// SurfaceErrorOnRetry returns a checker with the surfaceErrorOnRetry set
func (c *Checker) SurfaceErrorOnRetry() *Checker {
c.surfaceErrorOnRetry = true
return c
}
// WithCheck returns a checker with the provided check func
func (c *Checker) WithCheck(check func(context.Context) error) *Checker {
c.check = check
return c
}
// CheckResult encapsulates a check's identifying information and output
// Note there exists an analogous user-facing type, `cmd.check`, for output via
// `linkerd check -o json`.
type CheckResult struct {
Category CategoryID
Description string
HintURL string
Retry bool
Warning bool
Err error
}
// CheckObserver receives the results of each check.
type CheckObserver func(*CheckResult)
// Category is a group of checkers, to check a particular component or use-case
type Category struct {
ID CategoryID
checkers []Checker
enabled bool
// hintBaseURL provides a base URL with more information
// about the check
hintBaseURL string
}
// NewCategory returns an instance of Category with the specified data
func NewCategory(id CategoryID, checkers []Checker, enabled bool) *Category {
return &Category{
ID: id,
checkers: checkers,
enabled: enabled,
hintBaseURL: HintBaseURL(version.Version),
}
}
// WithHintBaseURL returns a Category with the provided hintBaseURL
func (c *Category) WithHintBaseURL(hintBaseURL string) *Category {
c.hintBaseURL = hintBaseURL
return c
}
// Options specifies configuration for a HealthChecker.
type Options struct {
IsMainCheckCommand bool
ControlPlaneNamespace string
CNINamespace string
DataPlaneNamespace string
KubeConfig string
KubeContext string
Impersonate string
ImpersonateGroup []string
APIAddr string
VersionOverride string
RetryDeadline time.Time
CNIEnabled bool
InstallManifest string
CRDManifest string
ChartValues *l5dcharts.Values
}
// HealthChecker encapsulates all health check checkers, and clients required to
// perform those checks.
type HealthChecker struct {
categories []*Category
*Options
// these fields are set in the process of running checks
kubeAPI *k8s.KubernetesAPI
kubeVersion *k8sVersion.Info
controlPlanePods []corev1.Pod
LatestVersions version.Channels
serverVersion string
linkerdConfig *l5dcharts.Values
uuid string
issuerCert *tls.Cred
trustAnchors []*x509.Certificate
cniDaemonSet *appsv1.DaemonSet
}
// Runner is implemented by any health-checkers that can be triggered with RunChecks()
type Runner interface {
RunChecks(observer CheckObserver) (bool, bool)
}
// NewHealthChecker returns an initialized HealthChecker
func NewHealthChecker(categoryIDs []CategoryID, options *Options) *HealthChecker {
hc := &HealthChecker{
Options: options,
}
hc.categories = hc.allCategories()
checkMap := map[CategoryID]struct{}{}
for _, category := range categoryIDs {
checkMap[category] = struct{}{}
}
for i := range hc.categories {
if _, ok := checkMap[hc.categories[i].ID]; ok {
hc.categories[i].enabled = true
}
}
return hc
}
func NewWithCoreChecks(options *Options) *HealthChecker {
checks := []CategoryID{KubernetesAPIChecks, LinkerdControlPlaneExistenceChecks}
return NewHealthChecker(checks, options)
}
// InitializeKubeAPIClient creates a client for the HealthChecker. It avoids
// having to require the KubernetesAPIChecks check to run in order for the
// HealthChecker to run other checks.
func (hc *HealthChecker) InitializeKubeAPIClient() error {
k8sAPI, err := k8s.NewAPI(hc.KubeConfig, hc.KubeContext, hc.Impersonate, hc.ImpersonateGroup, RequestTimeout)
if err != nil {
return err
}
hc.kubeAPI = k8sAPI
return nil
}
// InitializeLinkerdGlobalConfig populates the linkerd config object in the
// healthchecker. It avoids having to require the LinkerdControlPlaneExistenceChecks
// check to run before running other checks
func (hc *HealthChecker) InitializeLinkerdGlobalConfig(ctx context.Context) error {
uuid, l5dConfig, err := hc.checkLinkerdConfigConfigMap(ctx)
if err != nil {
return err
}
if l5dConfig != nil {
hc.CNIEnabled = l5dConfig.CNIEnabled
}
hc.uuid = uuid
hc.linkerdConfig = l5dConfig
return nil
}
// AppendCategories returns a HealthChecker instance appending the provided Categories
func (hc *HealthChecker) AppendCategories(categories ...*Category) *HealthChecker {
hc.categories = append(hc.categories, categories...)
return hc
}
// GetCategories returns all the categories
func (hc *HealthChecker) GetCategories() []*Category {
return hc.categories
}
// allCategories is the global, ordered list of all checkers, grouped by
// category. This method is attached to the HealthChecker struct because the
// checkers directly reference other members of the struct, such as kubeAPI,
// controlPlanePods, etc.
//
// Ordering is important because checks rely on specific `HealthChecker` members
// getting populated by earlier checks, such as kubeAPI, controlPlanePods, etc.
//
// Note that all checks should include a `hintAnchor` with a corresponding section
// in the linkerd check faq:
// https://linkerd.io/{major-version}/checks/#
func (hc *HealthChecker) allCategories() []*Category {
return []*Category{
NewCategory(
KubernetesAPIChecks,
[]Checker{
{
description: "can initialize the client",
hintAnchor: "k8s-api",
fatal: true,
check: func(context.Context) (err error) {
err = hc.InitializeKubeAPIClient()
return
},
},
{
description: "can query the Kubernetes API",
hintAnchor: "k8s-api",
fatal: true,
check: func(ctx context.Context) (err error) {
hc.kubeVersion, err = hc.kubeAPI.GetVersionInfo()
return
},
},
},
false,
),
NewCategory(
KubernetesVersionChecks,
[]Checker{
{
description: "is running the minimum Kubernetes API version",
hintAnchor: "k8s-version",
check: func(context.Context) error {
return hc.kubeAPI.CheckVersion(hc.kubeVersion)
},
},
},
false,
),
NewCategory(
LinkerdPreInstallChecks,
[]Checker{
{
description: "control plane namespace does not already exist",
hintAnchor: "pre-ns",
check: func(ctx context.Context) error {
return hc.CheckNamespace(ctx, hc.ControlPlaneNamespace, false)
},
},
{
description: "can create non-namespaced resources",
hintAnchor: "pre-k8s-cluster-k8s",
check: func(ctx context.Context) error {
return hc.checkCanCreateNonNamespacedResources(ctx)
},
},
{
description: "can create ServiceAccounts",
hintAnchor: "pre-k8s",
check: func(ctx context.Context) error {
return hc.checkCanCreate(ctx, hc.ControlPlaneNamespace, "", "v1", "serviceaccounts")
},
},
{
description: "can create Services",
hintAnchor: "pre-k8s",
check: func(ctx context.Context) error {
return hc.checkCanCreate(ctx, hc.ControlPlaneNamespace, "", "v1", "services")
},
},
{
description: "can create Deployments",
hintAnchor: "pre-k8s",
check: func(ctx context.Context) error {
return hc.checkCanCreate(ctx, hc.ControlPlaneNamespace, "apps", "v1", "deployments")
},
},
{
description: "can create CronJobs",
hintAnchor: "pre-k8s",
check: func(ctx context.Context) error {
return hc.checkCanCreate(ctx, hc.ControlPlaneNamespace, "batch", "v1beta1", "cronjobs")
},
},
{
description: "can create ConfigMaps",
hintAnchor: "pre-k8s",
check: func(ctx context.Context) error {
return hc.checkCanCreate(ctx, hc.ControlPlaneNamespace, "", "v1", "configmaps")
},
},
{
description: "can create Secrets",
hintAnchor: "pre-k8s",
check: func(ctx context.Context) error {
return hc.checkCanCreate(ctx, hc.ControlPlaneNamespace, "", "v1", "secrets")
},
},
{
description: "can read Secrets",
hintAnchor: "pre-k8s",
check: func(ctx context.Context) error {
return hc.checkCanGet(ctx, hc.ControlPlaneNamespace, "", "v1", "secrets")
},
},
{
description: "can read extension-apiserver-authentication configmap",
hintAnchor: "pre-k8s",
check: func(ctx context.Context) error {
return hc.checkExtensionAPIServerAuthentication(ctx)
},
},
{
description: "no clock skew detected",
hintAnchor: "pre-k8s-clock-skew",
warning: true,
check: func(ctx context.Context) error {
return hc.checkClockSkew(ctx)
},
},
},
false,
),
NewCategory(
LinkerdCRDChecks,
[]Checker{
{
description: "control plane CustomResourceDefinitions exist",
hintAnchor: "l5d-existence-crd",
fatal: true,
retryDeadline: hc.RetryDeadline,
check: func(ctx context.Context) error {
return CheckCustomResourceDefinitions(ctx, hc.kubeAPI, hc.CRDManifest)
},
},
},
false,
),
NewCategory(
LinkerdControlPlaneExistenceChecks,
[]Checker{
{
description: "'linkerd-config' config map exists",
hintAnchor: "l5d-existence-linkerd-config",
fatal: true,
check: func(ctx context.Context) (err error) {
err = hc.InitializeLinkerdGlobalConfig(ctx)
return
},
},
{
description: "heartbeat ServiceAccount exist",
hintAnchor: "l5d-existence-sa",
fatal: true,
check: func(ctx context.Context) error {
if hc.isHeartbeatDisabled() {
return nil
}
return hc.checkServiceAccounts(ctx, []string{"linkerd-heartbeat"}, hc.ControlPlaneNamespace, controlPlaneComponentsSelector())
},
},
{
description: "control plane replica sets are ready",
hintAnchor: "l5d-existence-replicasets",
retryDeadline: hc.RetryDeadline,
fatal: true,
check: func(ctx context.Context) error {
controlPlaneReplicaSet, err := hc.kubeAPI.GetReplicaSets(ctx, hc.ControlPlaneNamespace)
if err != nil {
return err
}
return checkControlPlaneReplicaSets(controlPlaneReplicaSet)
},
},
{
description: "no unschedulable pods",
hintAnchor: "l5d-existence-unschedulable-pods",
retryDeadline: hc.RetryDeadline,
surfaceErrorOnRetry: true,
warning: true,
check: func(ctx context.Context) error {
// do not save this into hc.controlPlanePods, as this check may
// succeed prior to all expected control plane pods being up
controlPlanePods, err := hc.kubeAPI.GetPodsByNamespace(ctx, hc.ControlPlaneNamespace)
if err != nil {
return err
}
return checkUnschedulablePods(controlPlanePods)
},
},
{
description: "control plane pods are ready",
hintAnchor: "l5d-api-control-ready",
retryDeadline: hc.RetryDeadline,
surfaceErrorOnRetry: true,
fatal: true,
check: func(ctx context.Context) error {
var err error
podList, err := hc.kubeAPI.CoreV1().Pods(hc.ControlPlaneNamespace).List(ctx, metav1.ListOptions{
LabelSelector: k8s.ControllerComponentLabel,
})
if err != nil {
return err
}
hc.controlPlanePods = podList.Items
return validateControlPlanePods(hc.controlPlanePods)
},
},
{
description: "cluster networks contains all node podCIDRs",
hintAnchor: "l5d-cluster-networks-cidr",
check: func(ctx context.Context) error {
// We explicitly initialize the config here so that we dont rely on the "l5d-existence-linkerd-config"
// check to set the clusterNetworks value, since `linkerd check config` will skip that check.
err := hc.InitializeLinkerdGlobalConfig(ctx)
if err != nil {
return err
}
return hc.checkClusterNetworks(ctx)
},
},
{
description: "cluster networks contains all pods",
hintAnchor: "l5d-cluster-networks-pods",
check: func(ctx context.Context) error {
return hc.checkClusterNetworksContainAllPods(ctx)
},
},
{
description: "cluster networks contains all services",
hintAnchor: "l5d-cluster-networks-pods",
check: func(ctx context.Context) error {
return hc.checkClusterNetworksContainAllServices(ctx)
},
},
},
false,
),
NewCategory(
LinkerdConfigChecks,
[]Checker{
{
description: "control plane Namespace exists",
hintAnchor: "l5d-existence-ns",
fatal: true,
check: func(ctx context.Context) error {
return hc.CheckNamespace(ctx, hc.ControlPlaneNamespace, true)
},
},
{
description: "control plane ClusterRoles exist",
hintAnchor: "l5d-existence-cr",
fatal: true,
check: func(ctx context.Context) error {
return hc.checkClusterRoles(ctx, true, hc.expectedRBACNames(), controlPlaneComponentsSelector())
},
},
{
description: "control plane ClusterRoleBindings exist",
hintAnchor: "l5d-existence-crb",
fatal: true,
check: func(ctx context.Context) error {
return hc.checkClusterRoleBindings(ctx, true, hc.expectedRBACNames(), controlPlaneComponentsSelector())
},
},
{
description: "control plane ServiceAccounts exist",
hintAnchor: "l5d-existence-sa",
fatal: true,
check: func(ctx context.Context) error {
return hc.checkServiceAccounts(ctx, ExpectedServiceAccountNames, hc.ControlPlaneNamespace, controlPlaneComponentsSelector())
},
},
{
description: "control plane CustomResourceDefinitions exist",
hintAnchor: "l5d-existence-crd",
fatal: true,
check: func(ctx context.Context) error {
return CheckCustomResourceDefinitions(ctx, hc.kubeAPI, hc.CRDManifest)
},
},
{
description: "control plane MutatingWebhookConfigurations exist",
hintAnchor: "l5d-existence-mwc",
fatal: true,
check: func(ctx context.Context) error {
return hc.checkMutatingWebhookConfigurations(ctx, true)
},
},
{
description: "control plane ValidatingWebhookConfigurations exist",
hintAnchor: "l5d-existence-vwc",
fatal: true,
check: func(ctx context.Context) error {
return hc.checkValidatingWebhookConfigurations(ctx, true)
},
},
{
description: "proxy-init container runs as root user if docker container runtime is used",
hintAnchor: "l5d-proxy-init-run-as-root",
fatal: false,
check: func(ctx context.Context) error {
// We explicitly initialize the config here so that we dont rely on the "l5d-existence-linkerd-config"
// check to set the clusterNetworks value, since `linkerd check config` will skip that check.
err := hc.InitializeLinkerdGlobalConfig(ctx)
if err != nil {
if kerrors.IsNotFound(err) {
return SkipError{Reason: configMapDoesNotExistSkipReason}
}
return err
}
config := hc.LinkerdConfig()
runAsRoot := config != nil && config.ProxyInit != nil && config.ProxyInit.RunAsRoot
if !runAsRoot {
return CheckNodesHaveNonDockerRuntime(ctx, hc.KubeAPIClient())
}
return nil
},
},
},
false,
),
NewCategory(
LinkerdCNIPluginChecks,
[]Checker{
{
description: "cni plugin ConfigMap exists",
hintAnchor: "cni-plugin-cm-exists",
fatal: true,
check: func(ctx context.Context) error {
if !hc.CNIEnabled {
return SkipError{Reason: linkerdCNIDisabledSkipReason}
}
_, err := hc.kubeAPI.CoreV1().ConfigMaps(hc.CNINamespace).Get(ctx, linkerdCNIConfigMapName, metav1.GetOptions{})
return err
},
},
{
description: "cni plugin ClusterRole exists",
hintAnchor: "cni-plugin-cr-exists",
fatal: true,
check: func(ctx context.Context) error {
if !hc.CNIEnabled {
return SkipError{Reason: linkerdCNIDisabledSkipReason}
}
_, err := hc.kubeAPI.RbacV1().ClusterRoles().Get(ctx, linkerdCNIResourceName, metav1.GetOptions{})
if kerrors.IsNotFound(err) {
return fmt.Errorf("missing ClusterRole: %s", linkerdCNIResourceName)
}
return err
},
},
{
description: "cni plugin ClusterRoleBinding exists",
hintAnchor: "cni-plugin-crb-exists",
fatal: true,
check: func(ctx context.Context) error {
if !hc.CNIEnabled {
return SkipError{Reason: linkerdCNIDisabledSkipReason}
}
_, err := hc.kubeAPI.RbacV1().ClusterRoleBindings().Get(ctx, linkerdCNIResourceName, metav1.GetOptions{})
if kerrors.IsNotFound(err) {
return fmt.Errorf("missing ClusterRoleBinding: %s", linkerdCNIResourceName)
}
return err
},
},
{
description: "cni plugin ServiceAccount exists",
hintAnchor: "cni-plugin-sa-exists",
fatal: true,
check: func(ctx context.Context) error {
if !hc.CNIEnabled {
return SkipError{Reason: linkerdCNIDisabledSkipReason}
}
_, err := hc.kubeAPI.CoreV1().ServiceAccounts(hc.CNINamespace).Get(ctx, linkerdCNIResourceName, metav1.GetOptions{})
if kerrors.IsNotFound(err) {
return fmt.Errorf("missing ServiceAccount: %s", linkerdCNIResourceName)
}
return err
},
},
{
description: "cni plugin DaemonSet exists",
hintAnchor: "cni-plugin-ds-exists",
fatal: true,
check: func(ctx context.Context) (err error) {
if !hc.CNIEnabled {
return SkipError{Reason: linkerdCNIDisabledSkipReason}
}
hc.cniDaemonSet, err = hc.kubeAPI.Interface.AppsV1().DaemonSets(hc.CNINamespace).Get(ctx, linkerdCNIResourceName, metav1.GetOptions{})
if kerrors.IsNotFound(err) {
return fmt.Errorf("missing DaemonSet: %s", linkerdCNIResourceName)
}
return err
},
},
{
description: "cni plugin pod is running on all nodes",
hintAnchor: "cni-plugin-ready",
retryDeadline: hc.RetryDeadline,
surfaceErrorOnRetry: true,
fatal: true,
check: func(ctx context.Context) (err error) {
if !hc.CNIEnabled {
return SkipError{Reason: linkerdCNIDisabledSkipReason}
}
hc.cniDaemonSet, err = hc.kubeAPI.Interface.AppsV1().DaemonSets(hc.CNINamespace).Get(ctx, linkerdCNIResourceName, metav1.GetOptions{})
if kerrors.IsNotFound(err) {
return fmt.Errorf("missing DaemonSet: %s", linkerdCNIResourceName)
}
scheduled := hc.cniDaemonSet.Status.DesiredNumberScheduled
ready := hc.cniDaemonSet.Status.NumberReady
if scheduled != ready {
return fmt.Errorf("number ready: %d, number scheduled: %d", ready, scheduled)
}
return nil
},
},
},
false,
),
NewCategory(
LinkerdIdentity,
[]Checker{
{
description: "certificate config is valid",
hintAnchor: "l5d-identity-cert-config-valid",
fatal: true,
check: func(ctx context.Context) (err error) {
hc.issuerCert, hc.trustAnchors, err = hc.checkCertificatesConfig(ctx)
return
},
},
{
description: "trust anchors are using supported crypto algorithm",
hintAnchor: "l5d-identity-trustAnchors-use-supported-crypto",
fatal: true,
check: func(context.Context) error {
var invalidAnchors []string
for _, anchor := range hc.trustAnchors {
if err := issuercerts.CheckTrustAnchorAlgoRequirements(anchor); err != nil {
invalidAnchors = append(invalidAnchors, fmt.Sprintf("* %v %s %s", anchor.SerialNumber, anchor.Subject.CommonName, err))
}
}
if len(invalidAnchors) > 0 {
return fmt.Errorf("Invalid trustAnchors:\n\t%s", strings.Join(invalidAnchors, "\n\t"))
}
return nil
},
},
{
description: "trust anchors are within their validity period",
hintAnchor: "l5d-identity-trustAnchors-are-time-valid",
fatal: true,
check: func(ctx context.Context) error {
var expiredAnchors []string
for _, anchor := range hc.trustAnchors {
if err := issuercerts.CheckCertValidityPeriod(anchor); err != nil {
expiredAnchors = append(expiredAnchors, fmt.Sprintf("* %v %s %s", anchor.SerialNumber, anchor.Subject.CommonName, err))
}
}
if len(expiredAnchors) > 0 {
return fmt.Errorf("Invalid anchors:\n\t%s", strings.Join(expiredAnchors, "\n\t"))
}
return nil
},
},
{
description: "trust anchors are valid for at least 60 days",
hintAnchor: "l5d-identity-trustAnchors-not-expiring-soon",
warning: true,
check: func(ctx context.Context) error {
var expiringAnchors []string
for _, anchor := range hc.trustAnchors {
if err := issuercerts.CheckExpiringSoon(anchor); err != nil {
expiringAnchors = append(expiringAnchors, fmt.Sprintf("* %v %s %s", anchor.SerialNumber, anchor.Subject.CommonName, err))
}
}
if len(expiringAnchors) > 0 {
return fmt.Errorf("Anchors expiring soon:\n\t%s", strings.Join(expiringAnchors, "\n\t"))
}
return nil
},
},
{
description: "issuer cert is using supported crypto algorithm",
hintAnchor: "l5d-identity-issuer-cert-uses-supported-crypto",
fatal: true,
check: func(context.Context) error {
if err := issuercerts.CheckIssuerCertAlgoRequirements(hc.issuerCert.Certificate); err != nil {
return fmt.Errorf("issuer certificate %w", err)
}
return nil
},
},
{
description: "issuer cert is within its validity period",
hintAnchor: "l5d-identity-issuer-cert-is-time-valid",
fatal: true,
check: func(ctx context.Context) error {
if err := issuercerts.CheckCertValidityPeriod(hc.issuerCert.Certificate); err != nil {
return fmt.Errorf("issuer certificate is %w", err)
}
return nil
},
},
{
description: "issuer cert is valid for at least 60 days",
warning: true,
hintAnchor: "l5d-identity-issuer-cert-not-expiring-soon",
check: func(context.Context) error {
if err := issuercerts.CheckExpiringSoon(hc.issuerCert.Certificate); err != nil {
return fmt.Errorf("issuer certificate %w", err)
}
return nil
},
},
{
description: "issuer cert is issued by the trust anchor",
hintAnchor: "l5d-identity-issuer-cert-issued-by-trust-anchor",
check: func(ctx context.Context) error {
return hc.issuerCert.Verify(tls.CertificatesToPool(hc.trustAnchors), "", time.Time{})
},
},
},
false,
),
NewCategory(
LinkerdWebhooksAndAPISvcTLS,
[]Checker{
{
description: "proxy-injector webhook has valid cert",
hintAnchor: "l5d-proxy-injector-webhook-cert-valid",
fatal: true,
check: func(ctx context.Context) (err error) {
anchors, err := hc.fetchProxyInjectorCaBundle(ctx)
if err != nil {
return err
}
cert, err := hc.FetchCredsFromSecret(ctx, hc.ControlPlaneNamespace, proxyInjectorTLSSecretName)
if kerrors.IsNotFound(err) {
cert, err = hc.FetchCredsFromOldSecret(ctx, hc.ControlPlaneNamespace, proxyInjectorOldTLSSecretName)
}
if err != nil {
return err
}
identityName := fmt.Sprintf("linkerd-proxy-injector.%s.svc", hc.ControlPlaneNamespace)
return hc.CheckCertAndAnchors(cert, anchors, identityName)
},
},
{
description: "proxy-injector cert is valid for at least 60 days",
warning: true,
hintAnchor: "l5d-proxy-injector-webhook-cert-not-expiring-soon",
check: func(ctx context.Context) error {
cert, err := hc.FetchCredsFromSecret(ctx, hc.ControlPlaneNamespace, proxyInjectorTLSSecretName)
if kerrors.IsNotFound(err) {
cert, err = hc.FetchCredsFromOldSecret(ctx, hc.ControlPlaneNamespace, proxyInjectorOldTLSSecretName)
}
if err != nil {
return err
}
return hc.CheckCertAndAnchorsExpiringSoon(cert)
},
},
{
description: "sp-validator webhook has valid cert",
hintAnchor: "l5d-sp-validator-webhook-cert-valid",
fatal: true,
check: func(ctx context.Context) (err error) {
anchors, err := hc.fetchWebhookCaBundle(ctx, k8s.SPValidatorWebhookConfigName)
if err != nil {
return err
}
cert, err := hc.FetchCredsFromSecret(ctx, hc.ControlPlaneNamespace, spValidatorTLSSecretName)
if kerrors.IsNotFound(err) {
cert, err = hc.FetchCredsFromOldSecret(ctx, hc.ControlPlaneNamespace, spValidatorOldTLSSecretName)
}
if err != nil {
return err
}
identityName := fmt.Sprintf("linkerd-sp-validator.%s.svc", hc.ControlPlaneNamespace)
return hc.CheckCertAndAnchors(cert, anchors, identityName)
},
},
{
description: "sp-validator cert is valid for at least 60 days",
warning: true,
hintAnchor: "l5d-sp-validator-webhook-cert-not-expiring-soon",
check: func(ctx context.Context) error {
cert, err := hc.FetchCredsFromSecret(ctx, hc.ControlPlaneNamespace, spValidatorTLSSecretName)
if kerrors.IsNotFound(err) {
cert, err = hc.FetchCredsFromOldSecret(ctx, hc.ControlPlaneNamespace, spValidatorOldTLSSecretName)
}
if err != nil {
return err
}
return hc.CheckCertAndAnchorsExpiringSoon(cert)
},
},
{
description: "policy-validator webhook has valid cert",
hintAnchor: "l5d-policy-validator-webhook-cert-valid",
fatal: true,
check: func(ctx context.Context) (err error) {
anchors, err := hc.fetchWebhookCaBundle(ctx, k8s.PolicyValidatorWebhookConfigName)
if kerrors.IsNotFound(err) {
return SkipError{Reason: "policy-validator not installed"}
}
if err != nil {
return err
}
cert, err := hc.FetchCredsFromSecret(ctx, hc.ControlPlaneNamespace, policyValidatorTLSSecretName)
if kerrors.IsNotFound(err) {
return SkipError{Reason: "policy-validator not installed"}
}
if err != nil {
return err
}
identityName := fmt.Sprintf("linkerd-policy-validator.%s.svc", hc.ControlPlaneNamespace)
return hc.CheckCertAndAnchors(cert, anchors, identityName)
},
},
{
description: "policy-validator cert is valid for at least 60 days",
warning: true,
hintAnchor: "l5d-policy-validator-webhook-cert-not-expiring-soon",
check: func(ctx context.Context) error {
cert, err := hc.FetchCredsFromSecret(ctx, hc.ControlPlaneNamespace, policyValidatorTLSSecretName)
if kerrors.IsNotFound(err) {
return SkipError{Reason: "policy-validator not installed"}
}
if err != nil {
return err
}
return hc.CheckCertAndAnchorsExpiringSoon(cert)
},
},
},
false,
),
NewCategory(
LinkerdIdentityDataPlane,
[]Checker{
{
description: "data plane proxies certificate match CA",
hintAnchor: "l5d-identity-data-plane-proxies-certs-match-ca",
warning: true,
check: func(ctx context.Context) error {
return hc.checkDataPlaneProxiesCertificate(ctx)
},
},
},
false,
),
NewCategory(
LinkerdVersionChecks,
[]Checker{
{
description: "can determine the latest version",
hintAnchor: "l5d-version-latest",
warning: true,
check: func(ctx context.Context) (err error) {
if hc.VersionOverride != "" {
hc.LatestVersions, err = version.NewChannels(hc.VersionOverride)
} else {
uuid := "unknown"
if hc.uuid != "" {
uuid = hc.uuid
}
hc.LatestVersions, err = version.GetLatestVersions(ctx, uuid, "cli")
}
return
},
},
{
description: "cli is up-to-date",
hintAnchor: "l5d-version-cli",
warning: true,
check: func(context.Context) error {
return hc.LatestVersions.Match(version.Version)
},
},
},
false,
),
NewCategory(
LinkerdControlPlaneVersionChecks,
[]Checker{
{
description: "can retrieve the control plane version",
hintAnchor: "l5d-version-control",
retryDeadline: hc.RetryDeadline,
fatal: true,
check: func(ctx context.Context) (err error) {
hc.serverVersion, err = GetServerVersion(ctx, hc.ControlPlaneNamespace, hc.kubeAPI)
return
},
},
{
description: "control plane is up-to-date",
hintAnchor: "l5d-version-control",
warning: true,
check: func(context.Context) error {
return hc.LatestVersions.Match(hc.serverVersion)
},
},
{
description: "control plane and cli versions match",
hintAnchor: "l5d-version-control",
warning: true,
check: func(context.Context) error {
if hc.serverVersion != version.Version {
return fmt.Errorf("control plane running %s but cli running %s", hc.serverVersion, version.Version)
}
return nil
},
},
},
false,
),
NewCategory(
LinkerdControlPlaneProxyChecks,
[]Checker{
{
description: "control plane proxies are healthy",
hintAnchor: "l5d-cp-proxy-healthy",
retryDeadline: hc.RetryDeadline,
surfaceErrorOnRetry: true,
fatal: true,
check: func(ctx context.Context) error {
return hc.CheckProxyHealth(ctx, hc.ControlPlaneNamespace, hc.ControlPlaneNamespace)
},
},
{
description: "control plane proxies are up-to-date",
hintAnchor: "l5d-cp-proxy-version",
warning: true,
check: func(ctx context.Context) error {
podList, err := hc.kubeAPI.CoreV1().Pods(hc.ControlPlaneNamespace).List(ctx, metav1.ListOptions{LabelSelector: k8s.ControllerNSLabel})
if err != nil {
return err
}
return hc.CheckProxyVersionsUpToDate(podList.Items)
},
},
{
description: "control plane proxies and cli versions match",
hintAnchor: "l5d-cp-proxy-cli-version",
warning: true,
check: func(ctx context.Context) error {
podList, err := hc.kubeAPI.CoreV1().Pods(hc.ControlPlaneNamespace).List(ctx, metav1.ListOptions{LabelSelector: k8s.ControllerNSLabel})
if err != nil {
return err
}
return CheckIfProxyVersionsMatchWithCLI(podList.Items)
},
},
},
false,
),
NewCategory(
LinkerdDataPlaneChecks,
[]Checker{
{
description: "data plane namespace exists",
hintAnchor: "l5d-data-plane-exists",
fatal: true,
check: func(ctx context.Context) error {
if hc.DataPlaneNamespace == "" {
// when checking proxies in all namespaces, this check is a no-op
return nil
}
return hc.CheckNamespace(ctx, hc.DataPlaneNamespace, true)
},
},
{
description: "data plane proxies are ready",
hintAnchor: "l5d-data-plane-ready",
retryDeadline: hc.RetryDeadline,
fatal: true,
check: func(ctx context.Context) error {
pods, err := hc.GetDataPlanePods(ctx)
if err != nil {
return err
}
return CheckPodsRunning(pods, hc.DataPlaneNamespace)
},
},
{
description: "data plane is up-to-date",
hintAnchor: "l5d-data-plane-version",
warning: true,
check: func(ctx context.Context) error {
pods, err := hc.GetDataPlanePods(ctx)
if err != nil {
return err
}
return hc.CheckProxyVersionsUpToDate(pods)
},
},
{
description: "data plane and cli versions match",
hintAnchor: "l5d-data-plane-cli-version",
warning: true,
check: func(ctx context.Context) error {
pods, err := hc.GetDataPlanePods(ctx)
if err != nil {
return err
}
return CheckIfProxyVersionsMatchWithCLI(pods)
},
},
{
description: "data plane pod labels are configured correctly",
hintAnchor: "l5d-data-plane-pod-labels",
warning: true,
check: func(ctx context.Context) error {
pods, err := hc.GetDataPlanePods(ctx)
if err != nil {
return err
}
return checkMisconfiguredPodsLabels(pods)
},
},
{
description: "data plane service labels are configured correctly",
hintAnchor: "l5d-data-plane-services-labels",
warning: true,
check: func(ctx context.Context) error {
services, err := hc.GetServices(ctx)
if err != nil {
return err
}
return checkMisconfiguredServiceLabels(services)
},
},
{
description: "data plane service annotations are configured correctly",
hintAnchor: "l5d-data-plane-services-annotations",
warning: true,
check: func(ctx context.Context) error {
services, err := hc.GetServices(ctx)
if err != nil {
return err
}
return checkMisconfiguredServiceAnnotations(services)
},
},
{
description: "opaque ports are properly annotated",
hintAnchor: "linkerd-opaque-ports-definition",
warning: true,
check: func(ctx context.Context) error {
return hc.checkMisconfiguredOpaquePortAnnotations(ctx)
},
},
},
false,
),
NewCategory(
LinkerdHAChecks,
[]Checker{
{
description: "multiple replicas of control plane pods",
hintAnchor: "l5d-control-plane-replicas",
retryDeadline: hc.RetryDeadline,
warning: true,
check: func(ctx context.Context) error {
if hc.isHA() {
return hc.checkMinReplicasAvailable(ctx)
}
return SkipError{Reason: "not run for non HA installs"}
},
},
},
false,
),
NewCategory(
LinkerdExtensionChecks,
[]Checker{
{
description: "namespace configuration for extensions",
warning: true,
hintAnchor: "l5d-extension-namespaces",
check: func(ctx context.Context) error {
return hc.checkExtensionNsLabels(ctx)
},
},
},
false,
),
}
}
// CheckProxyVersionsUpToDate checks if all the proxies are on the latest
// installed version
func (hc *HealthChecker) CheckProxyVersionsUpToDate(pods []corev1.Pod) error {
return CheckProxyVersionsUpToDate(pods, hc.LatestVersions)
}
// CheckProxyVersionsUpToDate checks if all the proxies are on the latest
// installed version
func CheckProxyVersionsUpToDate(pods []corev1.Pod, versions version.Channels) error {
outdatedPods := []string{}
for _, pod := range pods {
status := k8s.GetPodStatus(pod)
if status == string(corev1.PodRunning) {
proxyVersion := k8s.GetProxyVersion(pod)
if proxyVersion == "" {
continue
}
if err := versions.Match(proxyVersion); err != nil {
outdatedPods = append(outdatedPods, fmt.Sprintf("\t* %s (%s)", pod.Name, proxyVersion))
}
}
}
if versions.Empty() {
return errors.New("unable to determine version channel")
}
if len(outdatedPods) > 0 {
podList := strings.Join(outdatedPods, "\n")
return fmt.Errorf("some proxies are not running the current version:\n%s", podList)
}
return nil
}
// CheckIfProxyVersionsMatchWithCLI checks if the latest proxy version
// matches that of the CLI
func CheckIfProxyVersionsMatchWithCLI(pods []corev1.Pod) error {
for _, pod := range pods {
status := k8s.GetPodStatus(pod)
proxyVersion := k8s.GetProxyVersion(pod)
if status == string(corev1.PodRunning) && proxyVersion != "" && proxyVersion != version.Version {
return fmt.Errorf("%s running %s but cli running %s", pod.Name, proxyVersion, version.Version)
}
}
return nil
}
// CheckCertAndAnchors checks if the given cert and anchors are valid
func (hc *HealthChecker) CheckCertAndAnchors(cert *tls.Cred, trustAnchors []*x509.Certificate, identityName string) error {
// check anchors time validity
var expiredAnchors []string
for _, anchor := range trustAnchors {
if err := issuercerts.CheckCertValidityPeriod(anchor); err != nil {
expiredAnchors = append(expiredAnchors, fmt.Sprintf("* %v %s %s", anchor.SerialNumber, anchor.Subject.CommonName, err))
}
}
if len(expiredAnchors) > 0 {
return fmt.Errorf("anchors not within their validity period:\n\t%s", strings.Join(expiredAnchors, "\n\t"))
}
// check cert validity
if err := issuercerts.CheckCertValidityPeriod(cert.Certificate); err != nil {
return fmt.Errorf("certificate is %w", err)
}
if err := cert.Verify(tls.CertificatesToPool(trustAnchors), identityName, time.Time{}); err != nil {
return fmt.Errorf("cert is not issued by the trust anchor: %w", err)
}
return nil
}
// CheckProxyHealth checks for the data-plane proxies health in the given namespace
// These checks consist of status and identity
func (hc *HealthChecker) CheckProxyHealth(ctx context.Context, controlPlaneNamespace, namespace string) error {
podList, err := hc.kubeAPI.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{LabelSelector: k8s.ControllerNSLabel})
if err != nil {
return err
}
// Validate the status of the pods
err = CheckPodsRunning(podList.Items, controlPlaneNamespace)
if err != nil {
return err
}
// Check proxy certificates
return checkPodsProxiesCertificate(ctx, *hc.kubeAPI, namespace, controlPlaneNamespace)
}
// CheckCertAndAnchorsExpiringSoon checks if the given cert and anchors expire soon, and returns an
// error if they do.
func (hc *HealthChecker) CheckCertAndAnchorsExpiringSoon(cert *tls.Cred) error {
// check anchors not expiring soon
var expiringAnchors []string
for _, anchor := range cert.TrustChain {
anchor := anchor
if err := issuercerts.CheckExpiringSoon(anchor); err != nil {
expiringAnchors = append(expiringAnchors, fmt.Sprintf("* %v %s %s", anchor.SerialNumber, anchor.Subject.CommonName, err))
}
}
if len(expiringAnchors) > 0 {
return fmt.Errorf("Anchors expiring soon:\n\t%s", strings.Join(expiringAnchors, "\n\t"))
}
// check cert not expiring soon
if err := issuercerts.CheckExpiringSoon(cert.Certificate); err != nil {
return fmt.Errorf("certificate %w", err)
}
return nil
}
// CheckAPIService checks the status of the given API Service and returns an error if it's not running
func (hc *HealthChecker) CheckAPIService(ctx context.Context, serviceName string) error {
apiServiceClient, err := apiregistrationv1client.NewForConfig(hc.kubeAPI.Config)
if err != nil {
return err
}
apiStatus, err := apiServiceClient.APIServices().Get(ctx, serviceName, metav1.GetOptions{})
if err != nil {
return err
}
for _, condition := range apiStatus.Status.Conditions {
if condition.Type == "Available" {
if condition.Status == "True" {
return nil
}
return fmt.Errorf("%s: %s", condition.Reason, condition.Message)
}
}
return fmt.Errorf("%s service not available", apiStatus.Name)
}
func (hc *HealthChecker) checkMinReplicasAvailable(ctx context.Context) error {
faulty := []string{}
for _, component := range linkerdHAControlPlaneComponents {
conf, err := hc.kubeAPI.AppsV1().Deployments(hc.ControlPlaneNamespace).Get(ctx, component, metav1.GetOptions{})
if err != nil {
return err
}
if conf.Status.AvailableReplicas <= 1 {
faulty = append(faulty, component)
}
}
if len(faulty) > 0 {
return fmt.Errorf("not enough replicas available for %v", faulty)
}
return nil
}
// RunChecks runs all configured checkers, and passes the results of each
// check to the observer. If a check fails and is marked as fatal, then all
// remaining checks are skipped. If at least one check fails, RunChecks returns
// false; if all checks passed, RunChecks returns true. Checks which are
// designated as warnings will not cause RunCheck to return false, however.
func (hc *HealthChecker) RunChecks(observer CheckObserver) (bool, bool) {
success := true
warning := false
for _, c := range hc.categories {
if c.enabled {
for _, checker := range c.checkers {
checker := checker // pin
if checker.check != nil {
if !hc.runCheck(c, &checker, observer) {
if !checker.warning {
success = false
} else {
warning = true
}
if checker.fatal {
return success, warning
}
}
}
}
}
}
return success, warning
}
func (hc *HealthChecker) RunWithExitOnError() (bool, bool) {
return hc.RunChecks(func(result *CheckResult) {
if result.Retry {
fmt.Fprintln(os.Stderr, "Waiting for control plane to become available")
return
}
if result.Err != nil && !result.Warning {
var msg string
switch result.Category {
case KubernetesAPIChecks:
msg = "Cannot connect to Kubernetes"
case LinkerdControlPlaneExistenceChecks:
msg = "Cannot find Linkerd"
}
fmt.Fprintf(os.Stderr, "%s: %s\nValidate the install with: 'linkerd check'\n",
msg, result.Err)
os.Exit(1)
}
})
}
// LinkerdConfig gets the Linkerd configuration values.
func (hc *HealthChecker) LinkerdConfig() *l5dcharts.Values {
return hc.linkerdConfig
}
func (hc *HealthChecker) runCheck(category *Category, c *Checker, observer CheckObserver) bool {
for {
ctx, cancel := context.WithTimeout(context.Background(), RequestTimeout)
err := c.check(ctx)
cancel()
var se SkipError
if errors.As(err, &se) {
log.Debugf("Skipping check: %s. Reason: %s", c.description, se.Reason)
return true
}
checkResult := &CheckResult{
Category: category.ID,
Description: c.description,
Warning: c.warning,
HintURL: fmt.Sprintf("%s%s", category.hintBaseURL, c.hintAnchor),
}
var vs VerboseSuccess
if errors.As(err, &vs) {
checkResult.Description = fmt.Sprintf("%s\n%s", checkResult.Description, vs.Message)
} else if err != nil {
checkResult.Err = CategoryError{category.ID, err}
}
if checkResult.Err != nil && time.Now().Before(c.retryDeadline) {
checkResult.Retry = true
if !c.surfaceErrorOnRetry {
checkResult.Err = errors.New("waiting for check to complete")
}
log.Debugf("Retrying on error: %s", err)
observer(checkResult)
time.Sleep(retryWindow)
continue
}
observer(checkResult)
return checkResult.Err == nil
}
}
func controlPlaneComponentsSelector() string {
return fmt.Sprintf("%s,!%s", k8s.ControllerNSLabel, LinkerdCNIResourceLabel)
}
// KubeAPIClient returns a fully configured k8s API client. This client is
// only configured if the KubernetesAPIChecks are configured and run first.
func (hc *HealthChecker) KubeAPIClient() *k8s.KubernetesAPI {
return hc.kubeAPI
}
// UUID returns the UUID of the installation
func (hc *HealthChecker) UUID() string {
return hc.uuid
}
func (hc *HealthChecker) checkLinkerdConfigConfigMap(ctx context.Context) (string, *l5dcharts.Values, error) {
configMap, values, err := FetchCurrentConfiguration(ctx, hc.kubeAPI, hc.ControlPlaneNamespace)
if err != nil {
return "", nil, err
}
return string(configMap.GetUID()), values, nil
}
// Checks whether the configuration of the linkerd-identity-issuer is correct. This means:
// 1. There is a config map present with identity context
// 2. The scheme in the identity context corresponds to the format of the issuer secret
// 3. The trust anchors (if scheme == kubernetes.io/tls) in the secret equal the ones in config
// 4. The certs and key are parsable
func (hc *HealthChecker) checkCertificatesConfig(ctx context.Context) (*tls.Cred, []*x509.Certificate, error) {
_, values, err := FetchCurrentConfiguration(ctx, hc.kubeAPI, hc.ControlPlaneNamespace)
if err != nil {
return nil, nil, err
}
var data *issuercerts.IssuerCertData
if values.Identity.Issuer.Scheme == "" || values.Identity.Issuer.Scheme == k8s.IdentityIssuerSchemeLinkerd {
data, err = issuercerts.FetchIssuerData(ctx, hc.kubeAPI, values.IdentityTrustAnchorsPEM, hc.ControlPlaneNamespace)
} else {
data, err = issuercerts.FetchExternalIssuerData(ctx, hc.kubeAPI, hc.ControlPlaneNamespace)
}
if err != nil {
return nil, nil, err
}
issuerCreds, err := tls.ValidateAndCreateCreds(data.IssuerCrt, data.IssuerKey)
if err != nil {
return nil, nil, err
}
anchors, err := tls.DecodePEMCertificates(data.TrustAnchors)
if err != nil {
return nil, nil, err
}
return issuerCreds, anchors, nil
}
// FetchCurrentConfiguration retrieves the current Linkerd configuration
func FetchCurrentConfiguration(ctx context.Context, k kubernetes.Interface, controlPlaneNamespace string) (*corev1.ConfigMap, *l5dcharts.Values, error) {
// Get the linkerd-config values if present.
configMap, err := config.FetchLinkerdConfigMap(ctx, k, controlPlaneNamespace)
if err != nil {
return nil, nil, err
}
rawValues := configMap.Data["values"]
if rawValues == "" {
return configMap, nil, nil
}
// Convert into latest values, where global field is removed.
rawValuesBytes, err := config.RemoveGlobalFieldIfPresent([]byte(rawValues))
if err != nil {
return nil, nil, err
}
rawValues = string(rawValuesBytes)
var fullValues l5dcharts.Values
err = yaml.Unmarshal([]byte(rawValues), &fullValues)
if err != nil {
return nil, nil, err
}
return configMap, &fullValues, nil
}
func (hc *HealthChecker) fetchProxyInjectorCaBundle(ctx context.Context) ([]*x509.Certificate, error) {
mwh, err := hc.getProxyInjectorMutatingWebhook(ctx)
if err != nil {
return nil, err
}
caBundle, err := tls.DecodePEMCertificates(string(mwh.ClientConfig.CABundle))
if err != nil {
return nil, err
}
return caBundle, nil
}
func (hc *HealthChecker) fetchWebhookCaBundle(ctx context.Context, webhook string) ([]*x509.Certificate, error) {
vwc, err := hc.kubeAPI.AdmissionregistrationV1().ValidatingWebhookConfigurations().Get(ctx, webhook, metav1.GetOptions{})
if err != nil {
return nil, err
}
if len(vwc.Webhooks) != 1 {
return nil, fmt.Errorf("expected 1 webhooks, found %d", len(vwc.Webhooks))
}
caBundle, err := tls.DecodePEMCertificates(string(vwc.Webhooks[0].ClientConfig.CABundle))
if err != nil {
return nil, err
}
return caBundle, nil
}
// FetchTrustBundle retrieves the ca-bundle from the config-map linkerd-identity-trust-roots
func FetchTrustBundle(ctx context.Context, kubeAPI k8s.KubernetesAPI, controlPlaneNamespace string) (string, error) {
configMap, err := kubeAPI.CoreV1().ConfigMaps(controlPlaneNamespace).Get(ctx, "linkerd-identity-trust-roots", metav1.GetOptions{})
return configMap.Data["ca-bundle.crt"], err
}
// FetchCredsFromSecret retrieves the TLS creds given a secret name
func (hc *HealthChecker) FetchCredsFromSecret(ctx context.Context, namespace string, secretName string) (*tls.Cred, error) {
secret, err := hc.kubeAPI.CoreV1().Secrets(namespace).Get(ctx, secretName, metav1.GetOptions{})
if err != nil {
return nil, err
}
crt, ok := secret.Data[certKeyName]
if !ok {
return nil, fmt.Errorf("key %s needs to exist in secret %s", certKeyName, secretName)
}
key, ok := secret.Data[keyKeyName]
if !ok {
return nil, fmt.Errorf("key %s needs to exist in secret %s", keyKeyName, secretName)
}
cred, err := tls.ValidateAndCreateCreds(string(crt), string(key))
if err != nil {
return nil, err
}
return cred, nil
}
// FetchCredsFromOldSecret function can be removed in later versions, once either all webhook secrets are recreated for each update
// (see https://github.com/linkerd/linkerd2/issues/4813)
// or later releases are only expected to update from the new names.
func (hc *HealthChecker) FetchCredsFromOldSecret(ctx context.Context, namespace string, secretName string) (*tls.Cred, error) {
secret, err := hc.kubeAPI.CoreV1().Secrets(namespace).Get(ctx, secretName, metav1.GetOptions{})
if err != nil {
return nil, err
}
crt, ok := secret.Data[certOldKeyName]
if !ok {
return nil, fmt.Errorf("key %s needs to exist in secret %s", certOldKeyName, secretName)
}
key, ok := secret.Data[keyOldKeyName]
if !ok {
return nil, fmt.Errorf("key %s needs to exist in secret %s", keyOldKeyName, secretName)
}
cred, err := tls.ValidateAndCreateCreds(string(crt), string(key))
if err != nil {
return nil, err
}
return cred, nil
}
// CheckNamespace checks whether the given namespace exists, and returns an
// error if it does not match `shouldExist`.
func (hc *HealthChecker) CheckNamespace(ctx context.Context, namespace string, shouldExist bool) error {
exists, err := hc.kubeAPI.NamespaceExists(ctx, namespace)
if err != nil {
return err
}
if shouldExist && !exists {
return fmt.Errorf("The \"%s\" namespace does not exist", namespace)
}
if !shouldExist && exists {
return fmt.Errorf("The \"%s\" namespace already exists", namespace)
}
return nil
}
func (hc *HealthChecker) checkClusterNetworks(ctx context.Context) error {
nodes, err := hc.kubeAPI.GetNodes(ctx)
if err != nil {
return err
}
clusterNetworks := strings.Split(hc.linkerdConfig.ClusterNetworks, ",")
clusterIPNets := make([]*net.IPNet, len(clusterNetworks))
for i, clusterNetwork := range clusterNetworks {
_, clusterIPNets[i], err = net.ParseCIDR(clusterNetwork)
if err != nil {
return err
}
}
var badPodCIDRS []string
var podCIDRExists bool
for _, node := range nodes {
podCIDR := node.Spec.PodCIDR
if podCIDR == "" {
continue
}
podCIDRExists = true
podIP, podIPNet, err := net.ParseCIDR(podCIDR)
if err != nil {
return err
}
exists := cluterNetworksContainCIDR(clusterIPNets, podIPNet, podIP)
if !exists {
badPodCIDRS = append(badPodCIDRS, podCIDR)
}
}
// If none of the nodes exposed a podCIDR then we cannot verify the clusterNetworks.
if !podCIDRExists {
// DigitalOcean for example, doesn't expose spec.podCIDR (#6398)
return SkipError{Reason: podCIDRUnavailableSkipReason}
}
if len(badPodCIDRS) > 0 {
sort.Strings(badPodCIDRS)
return fmt.Errorf("node has podCIDR(s) %v which are not contained in the Linkerd clusterNetworks.\n\tTry installing linkerd via --set clusterNetworks=\"%s\"",
badPodCIDRS, strings.Join(badPodCIDRS, "\\,"))
}
return nil
}
func cluterNetworksContainCIDR(clusterIPNets []*net.IPNet, podIPNet *net.IPNet, podIP net.IP) bool {
for _, clusterIPNet := range clusterIPNets {
clusterIPMaskOnes, _ := clusterIPNet.Mask.Size()
podCIDRMaskOnes, _ := podIPNet.Mask.Size()
if clusterIPNet.Contains(podIP) && podCIDRMaskOnes >= clusterIPMaskOnes {
return true
}
}
return false
}
func clusterNetworksContainIP(clusterIPNets []*net.IPNet, ip string) bool {
for _, clusterIPNet := range clusterIPNets {
if clusterIPNet.Contains(net.ParseIP(ip)) {
return true
}
}
return false
}
func (hc *HealthChecker) checkClusterNetworksContainAllPods(ctx context.Context) error {
clusterNetworks := strings.Split(hc.linkerdConfig.ClusterNetworks, ",")
clusterIPNets := make([]*net.IPNet, len(clusterNetworks))
var err error
for i, clusterNetwork := range clusterNetworks {
_, clusterIPNets[i], err = net.ParseCIDR(clusterNetwork)
if err != nil {
return err
}
}
pods, err := hc.kubeAPI.CoreV1().Pods(corev1.NamespaceAll).List(ctx, metav1.ListOptions{})
if err != nil {
return err
}
for _, pod := range pods.Items {
if pod.Spec.HostNetwork {
continue
}
if len(pod.Status.PodIP) == 0 {
continue
}
if !clusterNetworksContainIP(clusterIPNets, pod.Status.PodIP) {
return fmt.Errorf("the Linkerd clusterNetworks [%q] do not include pod %s/%s (%s)", hc.linkerdConfig.ClusterNetworks, pod.Namespace, pod.Name, pod.Status.PodIP)
}
}
return nil
}
func (hc *HealthChecker) checkClusterNetworksContainAllServices(ctx context.Context) error {
clusterNetworks := strings.Split(hc.linkerdConfig.ClusterNetworks, ",")
clusterIPNets := make([]*net.IPNet, len(clusterNetworks))
var err error
for i, clusterNetwork := range clusterNetworks {
_, clusterIPNets[i], err = net.ParseCIDR(clusterNetwork)
if err != nil {
return err
}
}
svcs, err := hc.kubeAPI.CoreV1().Services(corev1.NamespaceAll).List(ctx, metav1.ListOptions{})
if err != nil {
return err
}
for _, svc := range svcs.Items {
clusterIP := svc.Spec.ClusterIP
if clusterIP != "" && clusterIP != "None" && !clusterNetworksContainIP(clusterIPNets, svc.Spec.ClusterIP) {
return fmt.Errorf("the Linkerd clusterNetworks [%q] do not include svc %s/%s (%s)", hc.linkerdConfig.ClusterNetworks, svc.Namespace, svc.Name, svc.Spec.ClusterIP)
}
}
return nil
}
func (hc *HealthChecker) expectedRBACNames() []string {
return []string{
fmt.Sprintf("linkerd-%s-identity", hc.ControlPlaneNamespace),
fmt.Sprintf("linkerd-%s-proxy-injector", hc.ControlPlaneNamespace),
}
}
func (hc *HealthChecker) checkClusterRoles(ctx context.Context, shouldExist bool, expectedNames []string, labelSelector string) error {
return CheckClusterRoles(ctx, hc.kubeAPI, shouldExist, expectedNames, labelSelector)
}
// CheckClusterRoles checks that the expected ClusterRoles exist.
func CheckClusterRoles(ctx context.Context, kubeAPI *k8s.KubernetesAPI, shouldExist bool, expectedNames []string, labelSelector string) error {
options := metav1.ListOptions{
LabelSelector: labelSelector,
}
crList, err := kubeAPI.RbacV1().ClusterRoles().List(ctx, options)
if err != nil {
return err
}
objects := []runtime.Object{}
for _, item := range crList.Items {
item := item // pin
objects = append(objects, &item)
}
return checkResources("ClusterRoles", objects, expectedNames, shouldExist)
}
func (hc *HealthChecker) checkClusterRoleBindings(ctx context.Context, shouldExist bool, expectedNames []string, labelSelector string) error {
return CheckClusterRoleBindings(ctx, hc.kubeAPI, shouldExist, expectedNames, labelSelector)
}
// CheckClusterRoleBindings checks that the expected ClusterRoleBindings exist.
func CheckClusterRoleBindings(ctx context.Context, kubeAPI *k8s.KubernetesAPI, shouldExist bool, expectedNames []string, labelSelector string) error {
options := metav1.ListOptions{
LabelSelector: labelSelector,
}
crbList, err := kubeAPI.RbacV1().ClusterRoleBindings().List(ctx, options)
if err != nil {
return err
}
objects := []runtime.Object{}
for _, item := range crbList.Items {
item := item // pin
objects = append(objects, &item)
}
return checkResources("ClusterRoleBindings", objects, expectedNames, shouldExist)
}
// CheckConfigMaps checks that the expected ConfigMaps exist.
func CheckConfigMaps(ctx context.Context, kubeAPI *k8s.KubernetesAPI, namespace string, shouldExist bool, expectedNames []string, labelSelector string) error {
options := metav1.ListOptions{
LabelSelector: labelSelector,
}
crbList, err := kubeAPI.CoreV1().ConfigMaps(namespace).List(ctx, options)
if err != nil {
return err
}
objects := []runtime.Object{}
for _, item := range crbList.Items {
item := item // pin
objects = append(objects, &item)
}
return checkResources("ConfigMaps", objects, expectedNames, shouldExist)
}
func (hc *HealthChecker) isHA() bool {
return hc.linkerdConfig.HighAvailability
}
func (hc *HealthChecker) isHeartbeatDisabled() bool {
return hc.linkerdConfig.DisableHeartBeat
}
func (hc *HealthChecker) checkServiceAccounts(ctx context.Context, saNames []string, ns, labelSelector string) error {
return CheckServiceAccounts(ctx, hc.kubeAPI, saNames, ns, labelSelector)
}
// CheckServiceAccounts check for serviceaccounts
func CheckServiceAccounts(ctx context.Context, api *k8s.KubernetesAPI, saNames []string, ns, labelSelector string) error {
options := metav1.ListOptions{
LabelSelector: labelSelector,
}
saList, err := api.CoreV1().ServiceAccounts(ns).List(ctx, options)
if err != nil {
return err
}
objects := []runtime.Object{}
for _, item := range saList.Items {
item := item // pin
objects = append(objects, &item)
}
return checkResources("ServiceAccounts", objects, saNames, true)
}
// CheckIfLinkerdExists checks if Linkerd exists
func CheckIfLinkerdExists(ctx context.Context, kubeAPI *k8s.KubernetesAPI, controlPlaneNamespace string) (bool, error) {
_, err := kubeAPI.CoreV1().Namespaces().Get(ctx, controlPlaneNamespace, metav1.GetOptions{})
if err != nil {
if kerrors.IsNotFound(err) {
return false, nil
}
return false, err
}
_, _, err = FetchCurrentConfiguration(ctx, kubeAPI, controlPlaneNamespace)
if err != nil {
if kerrors.IsNotFound(err) {
return false, nil
}
return false, err
}
return true, nil
}
func (hc *HealthChecker) getProxyInjectorMutatingWebhook(ctx context.Context) (*admissionRegistration.MutatingWebhook, error) {
mwc, err := hc.kubeAPI.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, k8s.ProxyInjectorWebhookConfigName, metav1.GetOptions{})
if err != nil {
return nil, err
}
if len(mwc.Webhooks) != 1 {
return nil, fmt.Errorf("expected 1 webhooks, found %d", len(mwc.Webhooks))
}
return &mwc.Webhooks[0], nil
}
func (hc *HealthChecker) checkMutatingWebhookConfigurations(ctx context.Context, shouldExist bool) error {
options := metav1.ListOptions{
LabelSelector: controlPlaneComponentsSelector(),
}
mwc, err := hc.kubeAPI.AdmissionregistrationV1().MutatingWebhookConfigurations().List(ctx, options)
if err != nil {
return err
}
objects := []runtime.Object{}
for _, item := range mwc.Items {
item := item // pin
objects = append(objects, &item)
}
return checkResources("MutatingWebhookConfigurations", objects, []string{k8s.ProxyInjectorWebhookConfigName}, shouldExist)
}
func (hc *HealthChecker) checkValidatingWebhookConfigurations(ctx context.Context, shouldExist bool) error {
options := metav1.ListOptions{
LabelSelector: controlPlaneComponentsSelector(),
}
vwc, err := hc.kubeAPI.AdmissionregistrationV1().ValidatingWebhookConfigurations().List(ctx, options)
if err != nil {
return err
}
objects := []runtime.Object{}
for _, item := range vwc.Items {
item := item // pin
objects = append(objects, &item)
}
return checkResources("ValidatingWebhookConfigurations", objects, []string{k8s.SPValidatorWebhookConfigName}, shouldExist)
}
// CheckCustomResourceDefinitions checks that all of the Linkerd CRDs are
// installed on the cluster.
func CheckCustomResourceDefinitions(ctx context.Context, k8sAPI *k8s.KubernetesAPI, expectedCRDManifests string) error {
crdYamls := strings.Split(expectedCRDManifests, "\n---\n")
crdVersions := []struct{ name, version string }{}
for _, crdYaml := range crdYamls {
var crd apiextv1.CustomResourceDefinition
err := yaml.Unmarshal([]byte(crdYaml), &crd)
if err != nil {
return err
}
if len(crd.Spec.Versions) == 0 {
continue
}
versionIndex := len(crd.Spec.Versions) - 1
crdVersions = append(crdVersions, struct{ name, version string }{
name: crd.Name,
version: crd.Spec.Versions[versionIndex].Name,
})
}
errMsgs := []string{}
for _, crdVersion := range crdVersions {
name := crdVersion.name
version := crdVersion.version
crd, err := k8sAPI.Apiextensions.ApiextensionsV1().CustomResourceDefinitions().Get(ctx, name, metav1.GetOptions{})
if err != nil && kerrors.IsNotFound(err) {
errMsgs = append(errMsgs, fmt.Sprintf("missing %s", name))
continue
} else if err != nil {
return err
}
if !crdHasVersion(crd, version) {
errMsgs = append(errMsgs, fmt.Sprintf("CRD %s is missing version %s", name, version))
}
}
if len(errMsgs) > 0 {
return errors.New(strings.Join(errMsgs, ", "))
}
return nil
}
func crdHasVersion(crd *apiextv1.CustomResourceDefinition, version string) bool {
for _, crdVersion := range crd.Spec.Versions {
if crdVersion.Name == version {
return true
}
}
return false
}
// CheckNodesHaveNonDockerRuntime checks that each node has a non-Docker
// runtime. This check is only called if proxyInit is not running as root
// which is a problem for clusters with a Docker container runtime.
func CheckNodesHaveNonDockerRuntime(ctx context.Context, k8sAPI *k8s.KubernetesAPI) error {
hasDockerNodes := false
continueToken := ""
for {
nodes, err := k8sAPI.CoreV1().Nodes().List(ctx, metav1.ListOptions{Continue: continueToken})
if err != nil {
return err
}
continueToken = nodes.Continue
for _, node := range nodes.Items {
crv := node.Status.NodeInfo.ContainerRuntimeVersion
if strings.HasPrefix(crv, "docker:") {
hasDockerNodes = true
break
}
}
if continueToken == "" {
break
}
}
if hasDockerNodes {
return fmt.Errorf("there are nodes using the docker container runtime and proxy-init container must run as root user.\ntry installing linkerd via --set proxyInit.runAsRoot=true")
}
return nil
}
// MeshedPodIdentityData contains meshed pod details + trust anchors of the proxy
type MeshedPodIdentityData struct {
Name string
Namespace string
Anchors string
}
// GetMeshedPodsIdentityData obtains the identity data (trust anchors) for all meshed pods
func GetMeshedPodsIdentityData(ctx context.Context, api kubernetes.Interface, dataPlaneNamespace string) ([]MeshedPodIdentityData, error) {
podList, err := api.CoreV1().Pods(dataPlaneNamespace).List(ctx, metav1.ListOptions{LabelSelector: k8s.ControllerNSLabel})
if err != nil {
return nil, err
}
if len(podList.Items) == 0 {
return nil, nil
}
pods := []MeshedPodIdentityData{}
for _, pod := range podList.Items {
containers := append(pod.Spec.InitContainers, pod.Spec.Containers...)
for _, containerSpec := range containers {
if containerSpec.Name != k8s.ProxyContainerName {
continue
}
for _, envVar := range containerSpec.Env {
if envVar.Name != identity.EnvTrustAnchors {
continue
}
pods = append(pods, MeshedPodIdentityData{
pod.Name, pod.Namespace, strings.TrimSpace(envVar.Value),
})
}
}
}
return pods, nil
}
func (hc *HealthChecker) checkDataPlaneProxiesCertificate(ctx context.Context) error {
return checkPodsProxiesCertificate(ctx, *hc.kubeAPI, hc.DataPlaneNamespace, hc.ControlPlaneNamespace)
}
func checkPodsProxiesCertificate(ctx context.Context, kubeAPI k8s.KubernetesAPI, targetNamespace, controlPlaneNamespace string) error {
meshedPods, err := GetMeshedPodsIdentityData(ctx, kubeAPI, targetNamespace)
if err != nil {
return err
}
trustAnchorsPem, err := FetchTrustBundle(ctx, kubeAPI, controlPlaneNamespace)
if err != nil {
return err
}
offendingPods := []string{}
for _, pod := range meshedPods {
// Skip control plane pods since they load their trust anchors from the linkerd-identity-trust-anchors configmap.
if pod.Namespace == controlPlaneNamespace {
continue
}
if strings.TrimSpace(pod.Anchors) != strings.TrimSpace(trustAnchorsPem) {
if targetNamespace == "" {
offendingPods = append(offendingPods, fmt.Sprintf("* %s/%s", pod.Namespace, pod.Name))
} else {
offendingPods = append(offendingPods, fmt.Sprintf("* %s", pod.Name))
}
}
}
if len(offendingPods) == 0 {
return nil
}
return fmt.Errorf("Some pods do not have the current trust bundle and must be restarted:\n\t%s", strings.Join(offendingPods, "\n\t"))
}
func checkResources(resourceName string, objects []runtime.Object, expectedNames []string, shouldExist bool) error {
if !shouldExist {
if len(objects) > 0 {
resources := []Resource{}
for _, obj := range objects {
m, err := meta.Accessor(obj)
if err != nil {
return err
}
res := Resource{name: m.GetName()}
gvks, _, err := k8s.ObjectKinds(obj)
if err == nil && len(gvks) > 0 {
res.groupVersionKind = gvks[0]
}
resources = append(resources, res)
}
return ResourceError{resourceName, resources}
}
return nil
}
expected := map[string]bool{}
for _, name := range expectedNames {
expected[name] = false
}
for _, obj := range objects {
metaObj, err := meta.Accessor(obj)
if err != nil {
return err
}
if _, ok := expected[metaObj.GetName()]; ok {
expected[metaObj.GetName()] = true
}
}
missing := []string{}
for name, found := range expected {
if !found {
missing = append(missing, name)
}
}
if len(missing) > 0 {
sort.Strings(missing)
return fmt.Errorf("missing %s: %s", resourceName, strings.Join(missing, ", "))
}
return nil
}
// Check if there's a pod with the "opaque ports" annotation defined but a
// service selecting the aforementioned pod doesn't define it
func (hc *HealthChecker) checkMisconfiguredOpaquePortAnnotations(ctx context.Context) error {
// Initialize and sync the kubernetes API
// This is used instead of `hc.kubeAPI` to limit multiple k8s API requests
// and use the caching logic in the shared informers
// TODO: move the shared informer code out of `controller/`, and into `pkg` to simplify the dependency tree.
kubeAPI := controllerK8s.NewClusterScopedAPI(hc.kubeAPI, nil, nil, "local", controllerK8s.Endpoint, controllerK8s.Pod, controllerK8s.Svc)
kubeAPI.Sync(ctx.Done())
services, err := kubeAPI.Svc().Lister().Services(hc.DataPlaneNamespace).List(labels.Everything())
if err != nil {
return err
}
var errStrings []string
for _, service := range services {
if service.Spec.ClusterIP == "None" {
// skip headless services; they're handled differently
continue
}
endpoints, err := kubeAPI.Endpoint().Lister().Endpoints(service.Namespace).Get(service.Name)
if err != nil {
return err
}
pods, err := getEndpointsPods(endpoints, kubeAPI, service.Namespace)
if err != nil {
return err
}
for pod := range pods {
err := misconfiguredOpaqueAnnotation(service, pod)
if err != nil {
errStrings = append(errStrings, fmt.Sprintf("\t* %s", err.Error()))
}
}
}
if len(errStrings) >= 1 {
return fmt.Errorf(strings.Join(errStrings, "\n "))
}
return nil
}
// getEndpointsPods takes a collection of endpoints and returns the set of all
// the pods that they target.
func getEndpointsPods(endpoints *corev1.Endpoints, kubeAPI *controllerK8s.API, namespace string) (map[*corev1.Pod]struct{}, error) {
pods := make(map[*corev1.Pod]struct{})
for _, subset := range endpoints.Subsets {
for _, addr := range subset.Addresses {
if addr.TargetRef != nil && addr.TargetRef.Kind == "Pod" {
pod, err := kubeAPI.Pod().Lister().Pods(namespace).Get(addr.TargetRef.Name)
if err != nil {
return nil, err
}
if _, ok := pods[pod]; !ok {
pods[pod] = struct{}{}
}
}
}
}
return pods, nil
}
func misconfiguredOpaqueAnnotation(service *corev1.Service, pod *corev1.Pod) error {
var svcPorts, podPorts []string
if v, ok := service.Annotations[k8s.ProxyOpaquePortsAnnotation]; ok {
svcPorts = strings.Split(v, ",")
}
if v, ok := pod.Annotations[k8s.ProxyOpaquePortsAnnotation]; ok {
podPorts = strings.Split(v, ",")
}
// First loop through the services opaque ports and assert that if the pod
// exposes a port that is targeted by one of these ports, then it is
// marked as opaque on the pod.
for _, p := range svcPorts {
port, err := strconv.Atoi(p)
if err != nil {
return fmt.Errorf("failed to convert %s to port number for pod %s", p, pod.Name)
}
err = checkPodPorts(service, pod, podPorts, port)
if err != nil {
return err
}
}
// Next loop through the pod's opaque ports and assert that if one of
// the ports is targeted by a service port, then it is marked as opaque
// on the service.
for _, p := range podPorts {
if util.ContainsString(p, svcPorts) {
// The service exposes p and is marked as opaque.
continue
}
port, err := strconv.Atoi(p)
if err != nil {
return fmt.Errorf("failed to convert %s to port number for pod %s", p, pod.Name)
}
// p is marked as opaque on the pod, but the service that selects it
// does not have it marked as opaque. We first check if the service
// exposes it as a service or integer targetPort.
ok, err := checkServiceIntPorts(service, svcPorts, port)
if err != nil {
return err
}
if ok {
// The service targets the port as an integer and is marked as
// opaque so continue checking other pod ports.
continue
}
// The service does not expose p as a service or integer targetPort.
// We now check if it targets it as a named port, and if so, that the
// service port is marked as opaque.
err = checkServiceNamePorts(service, pod, port, svcPorts)
if err != nil {
return err
}
}
return nil
}
func checkPodPorts(service *corev1.Service, pod *corev1.Pod, podPorts []string, port int) error {
for _, sp := range service.Spec.Ports {
if int(sp.Port) == port {
for _, c := range pod.Spec.Containers {
for _, cp := range c.Ports {
if cp.ContainerPort == sp.TargetPort.IntVal || cp.Name == sp.TargetPort.StrVal {
// The pod exposes a container port that would be
// targeted by this service port
var strPort string
if sp.TargetPort.Type == 0 {
strPort = strconv.Itoa(int(sp.TargetPort.IntVal))
} else {
strPort = strconv.Itoa(int(cp.ContainerPort))
}
if util.ContainsString(strPort, podPorts) {
return nil
}
return fmt.Errorf("service %s expects target port %s to be opaque; add it to pod %s %s annotation", service.Name, strPort, pod.Name, k8s.ProxyOpaquePortsAnnotation)
}
}
}
}
}
return nil
}
func checkServiceIntPorts(service *corev1.Service, svcPorts []string, port int) (bool, error) {
for _, p := range service.Spec.Ports {
if p.TargetPort.Type == 0 && p.TargetPort.IntVal == 0 {
if int(p.Port) == port {
// The service does not have a target port, so its service
// port should be marked as opaque.
return false, fmt.Errorf("service %s targets the opaque port %d; add it to its %s annotation", service.Name, port, k8s.ProxyOpaquePortsAnnotation)
}
}
if int(p.TargetPort.IntVal) == port {
svcPort := strconv.Itoa(int(p.Port))
if util.ContainsString(svcPort, svcPorts) {
// The service exposes svcPort which targets p and svcPort
// is properly as opaque.
return true, nil
}
return false, fmt.Errorf("service %s targets the opaque port %d through %d; add %d to its %s annotation", service.Name, port, p.Port, p.Port, k8s.ProxyOpaquePortsAnnotation)
}
}
return false, nil
}
func checkServiceNamePorts(service *corev1.Service, pod *corev1.Pod, port int, svcPorts []string) error {
for _, p := range service.Spec.Ports {
if p.TargetPort.StrVal == "" {
// The target port is not named so there is no named container
// port to check.
continue
}
for _, c := range pod.Spec.Containers {
for _, cp := range c.Ports {
if int(cp.ContainerPort) == port {
// This is the containerPort that maps to the opaque port
// we are currently checking.
if cp.Name == p.TargetPort.StrVal {
svcPort := strconv.Itoa(int(p.Port))
if util.ContainsString(svcPort, svcPorts) {
// The service targets the container port by name
// and is marked as opaque.
return nil
}
return fmt.Errorf("service %s targets the opaque port %s through %d; add %d to its %s annotation", service.Name, cp.Name, p.Port, p.Port, k8s.ProxyOpaquePortsAnnotation)
}
}
}
}
}
return nil
}
// GetDataPlanePods returns all the pods with data plane
func (hc *HealthChecker) GetDataPlanePods(ctx context.Context) ([]corev1.Pod, error) {
selector := fmt.Sprintf("%s=%s", k8s.ControllerNSLabel, hc.ControlPlaneNamespace)
podList, err := hc.kubeAPI.CoreV1().Pods(hc.DataPlaneNamespace).List(ctx, metav1.ListOptions{LabelSelector: selector})
if err != nil {
return nil, err
}
return podList.Items, nil
}
// GetServices returns all services within data plane namespace
func (hc *HealthChecker) GetServices(ctx context.Context) ([]corev1.Service, error) {
svcList, err := hc.kubeAPI.CoreV1().Services(hc.DataPlaneNamespace).List(ctx, metav1.ListOptions{})
if err != nil {
return nil, err
}
return svcList.Items, nil
}
func (hc *HealthChecker) checkCanCreate(ctx context.Context, namespace, group, version, resource string) error {
return CheckCanPerformAction(ctx, hc.kubeAPI, "create", namespace, group, version, resource)
}
func (hc *HealthChecker) checkCanCreateNonNamespacedResources(ctx context.Context) error {
var errs []string
dryRun := metav1.CreateOptions{DryRun: []string{metav1.DryRunAll}}
// Iterate over all resources in install manifest
installManifestReader := strings.NewReader(hc.Options.InstallManifest)
yamlReader := yamlDecoder.NewYAMLReader(bufio.NewReader(installManifestReader))
for {
// Read single object YAML
objYAML, err := yamlReader.Read()
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return fmt.Errorf("error reading install manifest: %w", err)
}
// Create unstructured object from YAML
objMap := map[string]interface{}{}
err = yaml.Unmarshal(objYAML, &objMap)
if err != nil {
return fmt.Errorf("error unmarshaling yaml object %s: %w", objYAML, err)
}
if len(objMap) == 0 {
// Ignore header blocks with only comments
continue
}
obj := &unstructured.Unstructured{Object: objMap}
// Skip namespaced resources (dry-run requires namespace to exist)
if obj.GetNamespace() != "" {
continue
}
// Attempt to create resource using dry-run
resource, _ := meta.UnsafeGuessKindToResource(obj.GroupVersionKind())
_, err = hc.kubeAPI.DynamicClient.Resource(resource).Create(ctx, obj, dryRun)
if err != nil {
errs = append(errs, fmt.Sprintf("cannot create %s/%s: %v", obj.GetKind(), obj.GetName(), err))
}
}
if len(errs) > 0 {
return errors.New(strings.Join(errs, "\n "))
}
return nil
}
func (hc *HealthChecker) checkCanGet(ctx context.Context, namespace, group, version, resource string) error {
return CheckCanPerformAction(ctx, hc.kubeAPI, "get", namespace, group, version, resource)
}
func (hc *HealthChecker) checkExtensionAPIServerAuthentication(ctx context.Context) error {
if hc.kubeAPI == nil {
return fmt.Errorf("unexpected error: Kubernetes ClientSet not initialized")
}
m, err := hc.kubeAPI.CoreV1().ConfigMaps(metav1.NamespaceSystem).Get(ctx, k8s.ExtensionAPIServerAuthenticationConfigMapName, metav1.GetOptions{})
if err != nil {
return err
}
if v, exists := m.Data[k8s.ExtensionAPIServerAuthenticationRequestHeaderClientCAFileKey]; !exists || v == "" {
return fmt.Errorf("--%s is not configured", k8s.ExtensionAPIServerAuthenticationRequestHeaderClientCAFileKey)
}
return nil
}
func (hc *HealthChecker) checkClockSkew(ctx context.Context) error {
if hc.kubeAPI == nil {
// we should never get here
return errors.New("unexpected error: Kubernetes ClientSet not initialized")
}
var clockSkewNodes []string
nodeList, err := hc.kubeAPI.CoreV1().Nodes().List(ctx, metav1.ListOptions{})
if err != nil {
return err
}
for _, node := range nodeList.Items {
for _, condition := range node.Status.Conditions {
// we want to check only KubeletReady condition and only execute if the node is ready
if condition.Type == corev1.NodeReady && condition.Status == corev1.ConditionTrue {
since := time.Since(condition.LastHeartbeatTime.Time)
if (since > AllowedClockSkew) || (since < -AllowedClockSkew) {
clockSkewNodes = append(clockSkewNodes, node.Name)
}
}
}
}
if len(clockSkewNodes) > 0 {
return fmt.Errorf("clock skew detected for node(s): %s", strings.Join(clockSkewNodes, ", "))
}
return nil
}
func (hc *HealthChecker) checkExtensionNsLabels(ctx context.Context) error {
if hc.kubeAPI == nil {
// oops something wrong happened
return errors.New("unexpected error: Kubernetes ClientSet not initialized")
}
namespaces, err := hc.kubeAPI.GetAllNamespacesWithExtensionLabel(ctx)
if err != nil {
return fmt.Errorf("unexpected error when retrieving namespaces: %w", err)
}
freq := make(map[string][]string)
for _, ns := range namespaces {
// We can guarantee the namespace has the extension label since we used
// a label selector when retrieving namespaces
ext := ns.Labels[k8s.LinkerdExtensionLabel]
// To make it easier to print, store already error-formatted namespace
// in freq table
freq[ext] = append(freq[ext], fmt.Sprintf("\t\t* %s", ns.Name))
}
errs := []string{}
for ext, namespaces := range freq {
if len(namespaces) == 1 {
continue
}
errs = append(errs, fmt.Sprintf("\t* label \"%s=%s\" is present on more than one namespace:\n%s", k8s.LinkerdExtensionLabel, ext, strings.Join(namespaces, "\n")))
}
if len(errs) > 0 {
return errors.New(strings.Join(
append([]string{"some extensions have invalid configuration"}, errs...), "\n"))
}
return nil
}
// CheckRoles checks that the expected roles exist.
func CheckRoles(ctx context.Context, kubeAPI *k8s.KubernetesAPI, shouldExist bool, namespace string, expectedNames []string, labelSelector string) error {
options := metav1.ListOptions{
LabelSelector: labelSelector,
}
crList, err := kubeAPI.RbacV1().Roles(namespace).List(ctx, options)
if err != nil {
return err
}
objects := []runtime.Object{}
for _, item := range crList.Items {
item := item // pin
objects = append(objects, &item)
}
return checkResources("Roles", objects, expectedNames, shouldExist)
}
// CheckRoleBindings checks that the expected RoleBindings exist.
func CheckRoleBindings(ctx context.Context, kubeAPI *k8s.KubernetesAPI, shouldExist bool, namespace string, expectedNames []string, labelSelector string) error {
options := metav1.ListOptions{
LabelSelector: labelSelector,
}
crbList, err := kubeAPI.RbacV1().RoleBindings(namespace).List(ctx, options)
if err != nil {
return err
}
objects := []runtime.Object{}
for _, item := range crbList.Items {
item := item // pin
objects = append(objects, &item)
}
return checkResources("RoleBindings", objects, expectedNames, shouldExist)
}
// CheckCanPerformAction checks if a given k8s client is authorized to perform a given action.
func CheckCanPerformAction(ctx context.Context, api *k8s.KubernetesAPI, verb, namespace, group, version, resource string) error {
if api == nil {
// we should never get here
return fmt.Errorf("unexpected error: Kubernetes ClientSet not initialized")
}
return k8s.ResourceAuthz(
ctx,
api,
namespace,
verb,
group,
version,
resource,
"",
)
}
// getPodStatuses returns a map of all Linkerd container statuses:
// component =>
//
// pod name =>
// container statuses
func getPodStatuses(pods []corev1.Pod) map[string]map[string][]corev1.ContainerStatus {
statuses := make(map[string]map[string][]corev1.ContainerStatus)
for _, pod := range pods {
if pod.Status.Phase == corev1.PodRunning && strings.HasPrefix(pod.Name, "linkerd-") {
parts := strings.Split(pod.Name, "-")
// All control plane pods should have a name that results in at least 4
// substrings when string.Split on '-'
if len(parts) >= 4 {
name := strings.Join(parts[1:len(parts)-2], "-")
if _, found := statuses[name]; !found {
statuses[name] = make(map[string][]corev1.ContainerStatus)
}
statuses[name][pod.Name] = pod.Status.ContainerStatuses
}
}
}
return statuses
}
func validateControlPlanePods(pods []corev1.Pod) error {
statuses := getPodStatuses(pods)
names := []string{"destination", "identity", "proxy-injector"}
for _, name := range names {
pods, found := statuses[name]
if !found {
return fmt.Errorf("No running pods for \"linkerd-%s\"", name)
}
var err error
var ready bool
for pod, containers := range pods {
containersReady := true
for _, container := range containers {
if !container.Ready {
// TODO: Save this as a warning, allow check to pass but let the user
// know there is at least one pod not ready. This might imply
// restructuring health checks to allow individual checks to return
// either fatal or warning, rather than setting this property at
// compile time.
err = fmt.Errorf("pod/%s container %s is not ready", pod, container.Name)
containersReady = false
}
}
if containersReady {
// at least one pod has all containers ready
ready = true
break
}
}
if !ready {
return err
}
}
return nil
}
func checkUnschedulablePods(pods []corev1.Pod) error {
for _, pod := range pods {
for _, condition := range pod.Status.Conditions {
if condition.Reason == corev1.PodReasonUnschedulable {
return fmt.Errorf("%s: %s", pod.Name, condition.Message)
}
}
}
return nil
}
func checkControlPlaneReplicaSets(rst []appsv1.ReplicaSet) error {
var errors []string
for _, rs := range rst {
for _, r := range rs.Status.Conditions {
if r.Type == appsv1.ReplicaSetReplicaFailure && r.Status == corev1.ConditionTrue {
errors = append(errors, fmt.Sprintf("%s: %s", r.Reason, r.Message))
}
}
}
if len(errors) > 0 {
return fmt.Errorf("%s", strings.Join(errors, "\n "))
}
return nil
}
// CheckForPods checks if the given deployments have pod resources present
func CheckForPods(pods []corev1.Pod, deployNames []string) error {
exists := make(map[string]bool)
for _, pod := range pods {
for label, value := range pod.Labels {
// When the label value is `linkerd.io/control-plane-component` or
// `component`, we'll take its value as the name of the deployment
// that the pod is part of
if label == k8s.ControllerComponentLabel || label == "component" {
exists[value] = true
}
}
}
for _, expected := range deployNames {
if !exists[expected] {
return fmt.Errorf("Could not find pods for deployment %s", expected)
}
}
return nil
}
// CheckPodsRunning checks if the given pods are in running state
// along with containers to be in ready state
func CheckPodsRunning(pods []corev1.Pod, namespace string) error {
if len(pods) == 0 {
msg := fmt.Sprintf("no \"%s\" containers found", k8s.ProxyContainerName)
if namespace != "" {
msg += fmt.Sprintf(" in the \"%s\" namespace", namespace)
}
return fmt.Errorf(msg)
}
for _, pod := range pods {
status := k8s.GetPodStatus(pod)
// Skip validating pods that have a status which indicates there would
// be no running proxy container.
switch status {
case "Completed", "NodeShutdown", "Shutdown", "Terminated":
continue
}
if status != string(corev1.PodRunning) && status != "Evicted" {
return fmt.Errorf("pod \"%s\" status is %s", pod.Name, pod.Status.Phase)
}
if !k8s.GetProxyReady(pod) {
return fmt.Errorf("container \"%s\" in pod \"%s\" is not ready", k8s.ProxyContainerName, pod.Name)
}
}
return nil
}
// CheckIfDataPlanePodsExist checks if the proxy is present in the given pods
func CheckIfDataPlanePodsExist(pods []corev1.Pod) error {
for _, pod := range pods {
if !containsProxy(pod) {
return fmt.Errorf("could not find proxy container for %s pod", pod.Name)
}
}
return nil
}
func containsProxy(pod corev1.Pod) bool {
containers := append(pod.Spec.InitContainers, pod.Spec.Containers...)
for _, containerSpec := range containers {
if containerSpec.Name == k8s.ProxyContainerName {
return true
}
}
return false
}
package healthcheck
import (
"context"
"github.com/linkerd/linkerd2/pkg/k8s"
)
// FuzzFetchCurrentConfiguration fuzzes the FetchCurrentConfiguration function.
func FuzzFetchCurrentConfiguration(data []byte) int {
clientset, err := k8s.NewFakeAPI(string(data))
if err != nil {
return 0
}
_, _, _ = FetchCurrentConfiguration(context.Background(), clientset, "linkerd")
return 1
}
package healthcheck
import (
"fmt"
"strings"
"github.com/linkerd/linkerd2/pkg/k8s"
"github.com/linkerd/linkerd2/pkg/util"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var (
validAsLabelOnly = []string{
k8s.DefaultExportedServiceSelector,
}
validAsAnnotationOnly = []string{
k8s.ProxyInjectAnnotation,
}
validAsAnnotationPrefixOnly = []string{
k8s.ProxyConfigAnnotationsPrefix,
k8s.ProxyConfigAnnotationsPrefixAlpha,
}
)
func checkMisconfiguredPodsLabels(pods []corev1.Pod) error {
var invalid []string
for _, pod := range pods {
invalidLabels := getMisconfiguredLabels(pod.ObjectMeta)
if len(invalidLabels) > 0 {
invalid = append(invalid,
fmt.Sprintf("\t* %s/%s\n\t\t%s", pod.Namespace, pod.Name, strings.Join(invalidLabels, "\n\t\t")))
}
}
if len(invalid) > 0 {
return fmt.Errorf("Some labels on data plane pods should be annotations:\n%s", strings.Join(invalid, "\n"))
}
return nil
}
func checkMisconfiguredServiceLabels(services []corev1.Service) error {
var invalid []string
for _, svc := range services {
invalidLabels := getMisconfiguredLabels(svc.ObjectMeta)
if len(invalidLabels) > 0 {
invalid = append(invalid,
fmt.Sprintf("\t* %s/%s\n\t\t%s", svc.Namespace, svc.Name, strings.Join(invalidLabels, "\n\t\t")))
}
}
if len(invalid) > 0 {
return fmt.Errorf("Some labels on data plane services should be annotations:\n%s", strings.Join(invalid, "\n"))
}
return nil
}
func checkMisconfiguredServiceAnnotations(services []corev1.Service) error {
var invalid []string
for _, svc := range services {
invalidAnnotations := getMisconfiguredAnnotations(svc.ObjectMeta)
if len(invalidAnnotations) > 0 {
invalid = append(invalid,
fmt.Sprintf("\t* %s/%s\n\t\t%s", svc.Namespace, svc.Name, strings.Join(invalidAnnotations, "\n\t\t")))
}
}
if len(invalid) > 0 {
return fmt.Errorf("Some annotations on data plane services should be labels:\n%s", strings.Join(invalid, "\n"))
}
return nil
}
func getMisconfiguredLabels(objectMeta metav1.ObjectMeta) []string {
var invalid []string
for label := range objectMeta.Labels {
if hasAnyPrefix(label, validAsAnnotationPrefixOnly) ||
util.ContainsString(label, validAsAnnotationOnly) {
invalid = append(invalid, label)
}
}
return invalid
}
func getMisconfiguredAnnotations(objectMeta metav1.ObjectMeta) []string {
var invalid []string
for ann := range objectMeta.Annotations {
if util.ContainsString(ann, validAsLabelOnly) {
invalid = append(invalid, ann)
}
}
return invalid
}
func hasAnyPrefix(str string, prefixes []string) bool {
for _, pref := range prefixes {
if strings.HasPrefix(str, pref) {
return true
}
}
return false
}
package healthcheck
import (
"encoding/json"
"fmt"
"io"
"os"
"regexp"
"strings"
"time"
"github.com/briandowns/spinner"
"github.com/fatih/color"
"github.com/mattn/go-isatty"
)
const (
// JSONOutput is used to specify the json output format
JSONOutput = "json"
// TableOutput is used to specify the table output format
TableOutput = "table"
// WideOutput is used to specify the wide output format
WideOutput = "wide"
// ShortOutput is used to specify the short output format
ShortOutput = "short"
// DefaultHintBaseURL is the default base URL on the linkerd.io website
// that all check hints for the latest linkerd version point to. Each
// check adds its own `hintAnchor` to specify a location on the page.
DefaultHintBaseURL = "https://linkerd.io/2/checks/#"
)
var (
okStatus = color.New(color.FgGreen, color.Bold).SprintFunc()("\u221A") // √
warnStatus = color.New(color.FgYellow, color.Bold).SprintFunc()("\u203C") // ‼
failStatus = color.New(color.FgRed, color.Bold).SprintFunc()("\u00D7") // ×
reStableVersion = regexp.MustCompile(`stable-(\d\.\d+)\.`)
)
// Checks describes the "checks" field on a CheckCLIOutput
type Checks string
const (
// ExtensionMetadataSubcommand is the subcommand name an extension must
// support in order to provide config metadata to the "linkerd" CLI.
ExtensionMetadataSubcommand = "_extension-metadata"
// Always run the check, regardless of cluster state
Always Checks = "always"
// // TODO:
// // Cluster informs "linkerd check" to only run this extension if there are
// // on-cluster resources.
// Cluster Checks = "cluster"
// // Never informs "linkerd check" to never run this extension.
// Never Checks = "never"
)
// ExtensionMetadataOutput contains the output of a _extension-metadata subcommand.
type ExtensionMetadataOutput struct {
Name string `json:"name"`
Checks Checks `json:"checks"`
}
// CheckResults contains a slice of CheckResult structs.
type CheckResults struct {
Results []CheckResult
}
// CheckOutput groups the check results for all categories
type CheckOutput struct {
Success bool `json:"success"`
Categories []*CheckCategory `json:"categories"`
}
// CheckCategory groups a series of check for a category
type CheckCategory struct {
Name CategoryID `json:"categoryName"`
Checks []*Check `json:"checks"`
}
// Check is a user-facing version of `healthcheck.CheckResult`, for output via
// `linkerd check -o json`.
type Check struct {
Description string `json:"description"`
Hint string `json:"hint,omitempty"`
Error string `json:"error,omitempty"`
Result CheckResultStr `json:"result"`
}
// RunChecks submits each of the individual CheckResult structs to the given
// observer.
func (cr CheckResults) RunChecks(observer CheckObserver) (bool, bool) {
success := true
warning := false
for _, result := range cr.Results {
result := result // Copy loop variable to make lint happy.
if result.Err != nil {
if !result.Warning {
success = false
} else {
warning = true
}
}
observer(&result)
}
return success, warning
}
// PrintChecksResult writes the checks result.
func PrintChecksResult(wout io.Writer, output string, success bool, warning bool) {
if output == JSONOutput {
return
}
switch success {
case true:
fmt.Fprintf(wout, "Status check results are %s\n", okStatus)
case false:
fmt.Fprintf(wout, "Status check results are %s\n", failStatus)
}
}
// RunChecks runs the checks that are part of hc
func RunChecks(wout io.Writer, werr io.Writer, hc Runner, output string) (bool, bool) {
if output == JSONOutput {
return runChecksJSON(wout, werr, hc)
}
return runChecksTable(wout, hc, output)
}
func runChecksTable(wout io.Writer, hc Runner, output string) (bool, bool) {
var lastCategory CategoryID
spin := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
spin.Writer = wout
// We set up different printing functions because we need to handle
// 2 check formatting output use cases:
// 1. the default check output in `table` format
// 2. the summarized output in `short` format
prettyPrintResults := func(result *CheckResult) {
lastCategory = printCategory(wout, lastCategory, result)
spin.Stop()
if result.Retry {
restartSpinner(spin, result)
return
}
status := getResultStatus(result)
printResultDescription(wout, status, result)
}
prettyPrintResultsShort := func(result *CheckResult) {
// bail out early and skip printing if we've got an okStatus
if result.Err == nil {
return
}
lastCategory = printCategory(wout, lastCategory, result)
spin.Stop()
if result.Retry {
restartSpinner(spin, result)
return
}
status := getResultStatus(result)
printResultDescription(wout, status, result)
}
var (
success bool
warning bool
)
switch output {
case ShortOutput:
success, warning = hc.RunChecks(prettyPrintResultsShort)
default:
success, warning = hc.RunChecks(prettyPrintResults)
}
// This ensures there is a newline separating check categories from each
// other as well as the check result. When running in ShortOutput mode and
// there are no warnings, there is no newline printed.
if output != ShortOutput || !success || warning {
fmt.Fprintln(wout)
}
return success, warning
}
// CheckResultStr is a string describing the result of a check
type CheckResultStr string
const (
CheckSuccess CheckResultStr = "success"
CheckWarn CheckResultStr = "warning"
CheckErr CheckResultStr = "error"
)
func runChecksJSON(wout io.Writer, werr io.Writer, hc Runner) (bool, bool) {
var categories []*CheckCategory
collectJSONOutput := func(result *CheckResult) {
if categories == nil || categories[len(categories)-1].Name != result.Category {
categories = append(categories, &CheckCategory{
Name: result.Category,
Checks: []*Check{},
})
}
if !result.Retry {
currentCategory := categories[len(categories)-1]
// ignore checks that are going to be retried, we want only final results
status := CheckSuccess
if result.Err != nil {
status = CheckErr
if result.Warning {
status = CheckWarn
}
}
currentCheck := &Check{
Description: result.Description,
Result: status,
}
if result.Err != nil {
currentCheck.Error = result.Err.Error()
if result.HintURL != "" {
currentCheck.Hint = result.HintURL
}
}
currentCategory.Checks = append(currentCategory.Checks, currentCheck)
}
}
success, warning := hc.RunChecks(collectJSONOutput)
outputJSON := CheckOutput{
Success: success,
Categories: categories,
}
resultJSON, err := json.MarshalIndent(outputJSON, "", " ")
if err == nil {
fmt.Fprintf(wout, "%s\n", string(resultJSON))
} else {
fmt.Fprintf(werr, "JSON serialization of the check result failed with %s", err)
}
return success, warning
}
func printResultDescription(wout io.Writer, status string, result *CheckResult) {
fmt.Fprintf(wout, "%s %s\n", status, result.Description)
if result.Err == nil {
return
}
fmt.Fprintf(wout, " %s\n", result.Err)
if result.HintURL != "" {
fmt.Fprintf(wout, " see %s for hints\n", result.HintURL)
}
}
func getResultStatus(result *CheckResult) string {
status := okStatus
if result.Err != nil {
status = failStatus
if result.Warning {
status = warnStatus
}
}
return status
}
func restartSpinner(spin *spinner.Spinner, result *CheckResult) {
if isatty.IsTerminal(os.Stdout.Fd()) {
spin.Suffix = fmt.Sprintf(" %s", result.Err)
spin.Color("bold") // this calls spin.Restart()
}
}
func printCategory(wout io.Writer, lastCategory CategoryID, result *CheckResult) CategoryID {
if lastCategory == result.Category {
return lastCategory
}
if lastCategory != "" {
fmt.Fprintln(wout)
}
fmt.Fprintln(wout, result.Category)
fmt.Fprintln(wout, strings.Repeat("-", len(result.Category)))
return result.Category
}
// HintBaseURL returns the base URL on the linkerd.io website that check hints
// point to, depending on the version
func HintBaseURL(ver string) string {
stableVersion := reStableVersion.FindStringSubmatch(ver)
if stableVersion == nil {
return DefaultHintBaseURL
}
return fmt.Sprintf("https://linkerd.io/%s/checks/#", stableVersion[1])
}
package healthcheck
import (
"strings"
"github.com/linkerd/linkerd2/pkg/k8s"
corev1 "k8s.io/api/core/v1"
)
// HasExistingSidecars returns true if the pod spec already has the proxy init
// and sidecar containers injected. Otherwise, it returns false.
func HasExistingSidecars(podSpec *corev1.PodSpec) bool {
containers := append(podSpec.InitContainers, podSpec.Containers...)
for _, container := range containers {
if strings.HasPrefix(container.Image, "cr.l5d.io/linkerd/proxy:") ||
strings.HasPrefix(container.Image, "gcr.io/istio-release/proxyv2:") ||
container.Name == k8s.ProxyContainerName ||
container.Name == "istio-proxy" {
return true
}
}
for _, ic := range podSpec.InitContainers {
if strings.HasPrefix(ic.Image, "cr.l5d.io/linkerd/proxy-init:") ||
strings.HasPrefix(ic.Image, "gcr.io/istio-release/proxy_init:") ||
ic.Name == "linkerd-init" ||
ic.Name == "istio-init" {
return true
}
}
return false
}
package healthcheck
import (
"context"
"fmt"
"github.com/linkerd/linkerd2/pkg/config"
"github.com/linkerd/linkerd2/pkg/charts/linkerd2"
"github.com/linkerd/linkerd2/pkg/k8s"
)
// GetServerVersion returns Linkerd's version, as set in linkerd-config
func GetServerVersion(ctx context.Context, controlPlaneNamespace string, kubeAPI *k8s.KubernetesAPI) (string, error) {
cm, err := config.FetchLinkerdConfigMap(ctx, kubeAPI, controlPlaneNamespace)
if err != nil {
return "", fmt.Errorf("failed to fetch linkerd-config: %w", err)
}
values, err := linkerd2.ValuesFromConfigMap(cm)
if err != nil {
return "", fmt.Errorf("failed to load values from linkerd-config: %w", err)
}
return values.LinkerdVersion, nil
}
package identity
import (
"context"
pb "github.com/linkerd/linkerd2-proxy-api/go/identity"
"github.com/linkerd/linkerd2/pkg/tls"
fuzz "github.com/AdaLogics/go-fuzz-headers"
)
// FuzzServiceCertify fuzzes the Service.Certify method.
func FuzzServiceCertify(data []byte) int {
f := fuzz.NewConsumer(data)
req := &pb.CertifyRequest{}
err := f.GenerateStruct(req)
if err != nil {
return 0
}
svc := NewService(&fakeValidator{"successful-result", nil}, nil, nil, nil, "", "", "")
svc.updateIssuer(&fakeIssuer{tls.Crt{}, nil})
_, _ = svc.Certify(context.Background(), req)
return 1
}
package identity
import (
"context"
"crypto/sha256"
"crypto/x509"
"encoding/hex"
"errors"
"fmt"
"strings"
"sync"
"time"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
pb "github.com/linkerd/linkerd2-proxy-api/go/identity"
"github.com/linkerd/linkerd2/pkg/tls"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb"
)
const (
// DefaultIssuanceLifetime is the default lifetime of certificates issued by
// the identity service.
DefaultIssuanceLifetime = 24 * time.Hour
// EnvTrustAnchors is the environment variable holding the trust anchors for
// the proxy identity.
EnvTrustAnchors = "LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS"
eventTypeSkipped = "IssuerUpdateSkipped"
eventTypeUpdated = "IssuerUpdated"
eventTypeFailed = "IssuerValidationFailed"
eventTypeIssuedLeafCert = "IssuedLeafCertificate"
)
type (
// Service implements the gRPC service in terms of a Validator and Issuer.
Service struct {
pb.UnimplementedIdentityServer
validator Validator
trustAnchors *x509.CertPool
issuer *tls.Issuer
issuerMutex *sync.RWMutex
validity *tls.Validity
recordEvent func(parent runtime.Object, eventType, reason, message string)
expectedName, issuerPathCrt, issuerPathKey string
}
// Validator implementors accept a bearer token, validates it, and returns a
// DNS-form identity.
Validator interface {
// Validate takes an opaque authentication token, attempts to validate its
// authenticity, and produces a DNS-like identifier.
//
// An InvalidToken error should be returned if the provided token was not in a
// correct form.
//
// A NotAuthenticated error should be returned if the authenticity of the
// token cannot be validated.
Validate(context.Context, []byte) (string, error)
}
// InvalidToken is an error type returned by Validators to indicate that the
// provided authentication token was not valid.
InvalidToken struct{ Reason string }
// NotAuthenticated is an error type returned by Validators to indicate that the
// provided authentication token could not be authenticated.
NotAuthenticated struct{}
)
// Initialize loads the issuer certs from disk so it can start service CSRs to proxies
func (svc *Service) Initialize() error {
credentials, err := svc.loadCredentials()
if err != nil {
return err
}
svc.updateIssuer(credentials)
return nil
}
func (svc *Service) updateIssuer(newIssuer tls.Issuer) {
svc.issuerMutex.Lock()
svc.issuer = &newIssuer
log.Debug("Issuer has been updated")
svc.issuerMutex.Unlock()
}
// Run reads from the issuer and error channels and reloads the issuer certs when necessary
func (svc *Service) Run(issuerEvent <-chan struct{}, issuerError <-chan error) {
for {
select {
case <-issuerEvent:
if err := svc.Initialize(); err != nil {
message := fmt.Sprintf("Skipping issuer update as certs could not be read from disk: %s", err)
log.Warn(message)
svc.recordEvent(nil, v1.EventTypeWarning, eventTypeSkipped, message)
} else {
message := "Updated identity issuer"
log.Infof(message)
svc.recordEvent(nil, v1.EventTypeNormal, eventTypeUpdated, message)
}
case err := <-issuerError:
log.Warnf("Received error from fs watcher: %s", err)
}
}
}
func (svc *Service) loadCredentials() (tls.Issuer, error) {
creds, err := tls.ReadPEMCreds(
svc.issuerPathKey,
svc.issuerPathCrt,
)
if err != nil {
return nil, fmt.Errorf("failed to read CA from disk: %w", err)
}
// Don't verify with dns name as this is not a leaf certificate
if err := creds.Crt.Verify(svc.trustAnchors, "", time.Time{}); err != nil {
return nil, fmt.Errorf("failed to verify issuer credentials for '%s' with trust anchors: %w", svc.expectedName, err)
}
if !creds.Certificate.IsCA {
return nil, fmt.Errorf("failed to verify issuer certificate: it must be an intermediate-CA, but it is not")
}
log.Debugf("Loaded issuer cert: %s", creds.EncodeCertificatePEM())
return tls.NewCA(*creds, *svc.validity), nil
}
// NewService creates a new identity service.
func NewService(validator Validator, trustAnchors *x509.CertPool, validity *tls.Validity, recordEvent func(parent runtime.Object, eventType, reason, message string), expectedName, issuerPathCrt, issuerPathKey string) *Service {
return &Service{
pb.UnimplementedIdentityServer{},
validator,
trustAnchors,
nil,
&sync.RWMutex{},
validity,
recordEvent,
expectedName,
issuerPathCrt,
issuerPathKey,
}
}
// Register registers an identity service implementation in the provided gRPC
// server.
func Register(g *grpc.Server, s *Service) {
pb.RegisterIdentityServer(g, s)
}
// ensureIssuerStillValid should check that the CA is still good time wise
// and verifies just fine with the provided trust anchors
func (svc *Service) ensureIssuerStillValid() error {
issuer := *svc.issuer
switch is := issuer.(type) {
case *tls.CA:
// Don't verify with dns name as this is not a leaf certificate
return is.Cred.Verify(svc.trustAnchors, "", time.Time{})
default:
return fmt.Errorf("unsupported issuer type. Expected *tls.CA, got %v", is)
}
}
// Certify validates identity and signs certificates.
func (svc *Service) Certify(ctx context.Context, req *pb.CertifyRequest) (*pb.CertifyResponse, error) {
svc.issuerMutex.RLock()
defer svc.issuerMutex.RUnlock()
if svc.issuer == nil {
log.Warn("Certificate issuer is not ready")
return nil, status.Error(codes.Unavailable, "cert issuer not ready yet")
}
// Extract the relevant info from the request.
reqIdentity, tok, csr, err := checkRequest(req)
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
if err := svc.ensureIssuerStillValid(); err != nil {
log.Errorf("could not process CSR because of CA cert validation failure: %s - CSR Identity : %s", err, reqIdentity)
message := fmt.Sprintf("%s - CSR Identity : %s", err.Error(), reqIdentity)
svc.recordEvent(nil, v1.EventTypeWarning, eventTypeFailed, message)
return nil, err
}
if err = checkCSR(csr, reqIdentity); err != nil {
log.Debugf("requester sent invalid CSR: %s", err)
return nil, status.Error(codes.FailedPrecondition, err.Error())
}
// Authenticate the provided token against the Kubernetes API.
log.Debugf("Validating token for %s", reqIdentity)
tokIdentity, err := svc.validator.Validate(ctx, tok)
if err != nil {
var nae NotAuthenticated
if errors.As(err, &nae) {
log.Infof("authentication failed for %s: %s", reqIdentity, nae)
return nil, status.Error(codes.FailedPrecondition, nae.Error())
}
var ite InvalidToken
if errors.As(err, &ite) {
log.Infof("invalid token provided for %s: %s", reqIdentity, ite)
return nil, status.Error(codes.InvalidArgument, ite.Error())
}
msg := fmt.Sprintf("error validating token for %s: %s", reqIdentity, err)
log.Error(msg)
return nil, status.Error(codes.Internal, msg)
}
// Ensure the requested identity matches the token's identity.
if reqIdentity != tokIdentity {
msg := fmt.Sprintf("requested identity did not match provided token: requested=%s; found=%s",
reqIdentity, tokIdentity)
log.Info(msg)
return nil, status.Error(codes.FailedPrecondition, msg)
}
// Create a certificate
issuer := *svc.issuer
crt, err := issuer.IssueEndEntityCrt(csr)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
crts := crt.ExtractRaw()
if len(crts) == 0 {
//nolint:gocritic
log.Fatal("the issuer provided a certificate without key material")
}
validUntil := timestamppb.New(crt.Certificate.NotAfter)
hasher := sha256.New()
hasher.Write(crts[0])
hash := hex.EncodeToString(hasher.Sum(nil))
identitySegments := strings.Split(tokIdentity, ".")
msg := fmt.Sprintf("issued certificate for %s until %s: %s", tokIdentity, crt.Certificate.NotAfter, hash)
sa := v1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: identitySegments[0],
Namespace: identitySegments[1],
},
}
svc.recordEvent(&sa, v1.EventTypeNormal, eventTypeIssuedLeafCert, msg)
log.Info(msg)
// Bundle issuer crt with certificate so the trust path to the root can be verified.
rsp := &pb.CertifyResponse{
LeafCertificate: crts[0],
IntermediateCertificates: crts[1:],
ValidUntil: validUntil,
}
return rsp, nil
}
func checkRequest(req *pb.CertifyRequest) (string, []byte, *x509.CertificateRequest, error) {
reqIdentity := req.GetIdentity()
if reqIdentity == "" {
return "", nil, nil, errors.New("missing identity")
}
tok := req.GetToken()
if len(tok) == 0 {
return "", nil, nil, errors.New("missing token")
}
der := req.GetCertificateSigningRequest()
if len(der) == 0 {
return "", nil, nil,
errors.New("missing certificate signing request")
}
csr, err := x509.ParseCertificateRequest(der)
if err != nil {
return "", nil, nil, err
}
return reqIdentity, tok, csr, nil
}
func checkCSR(csr *x509.CertificateRequest, identity string) error {
if len(csr.DNSNames) != 1 {
return errors.New("CSR must have exactly one DNSName")
}
if csr.DNSNames[0] != identity {
return fmt.Errorf("CSR name does not match requested identity: csr=%s; req=%s", csr.DNSNames[0], identity)
}
switch csr.Subject.CommonName {
case "", identity:
default:
return fmt.Errorf("invalid CommonName: %s", csr.Subject.CommonName)
}
if len(csr.EmailAddresses) > 0 {
return errors.New("cannot validate email addresses")
}
if len(csr.IPAddresses) > 0 {
return errors.New("cannot validate IP addresses")
}
if len(csr.URIs) > 0 {
return errors.New("cannot validate URIs")
}
return nil
}
func (NotAuthenticated) Error() string {
return "authentication token could not be authenticated"
}
func (e InvalidToken) Error() string {
return e.Reason
}
package identity
import (
"context"
"crypto/x509"
"github.com/linkerd/linkerd2/pkg/tls"
)
type fakeValidator struct {
result string
err error
}
func (fk *fakeValidator) Validate(context.Context, []byte) (string, error) {
return fk.result, fk.err
}
type fakeIssuer struct {
result tls.Crt
err error
}
func (fi *fakeIssuer) IssueEndEntityCrt(*x509.CertificateRequest) (tls.Crt, error) {
return fi.result, fi.err
}
package inject
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"html/template"
"net"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
"time"
jsonfilter "github.com/clarketm/json"
"github.com/linkerd/linkerd2/pkg/charts"
l5dcharts "github.com/linkerd/linkerd2/pkg/charts/linkerd2"
"github.com/linkerd/linkerd2/pkg/charts/static"
"github.com/linkerd/linkerd2/pkg/k8s"
"github.com/linkerd/linkerd2/pkg/util"
log "github.com/sirupsen/logrus"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/chartutil"
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
k8sResource "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr"
"sigs.k8s.io/yaml"
)
var (
rTrail = regexp.MustCompile(`\},\s*\]`)
// ProxyAnnotations is the list of possible annotations that can be applied on a pod or namespace.
// All these annotations should be prefixed with "config.linkerd.io"
ProxyAnnotations = []string{
k8s.ProxyAdminPortAnnotation,
k8s.ProxyControlPortAnnotation,
k8s.ProxyEnableDebugAnnotation,
k8s.ProxyEnableExternalProfilesAnnotation,
k8s.ProxyImagePullPolicyAnnotation,
k8s.ProxyInboundPortAnnotation,
k8s.ProxyInitImageAnnotation,
k8s.ProxyInitImageVersionAnnotation,
k8s.ProxyOutboundPortAnnotation,
k8s.ProxyPodInboundPortsAnnotation,
k8s.ProxyCPULimitAnnotation,
k8s.ProxyCPURequestAnnotation,
k8s.ProxyImageAnnotation,
k8s.ProxyAdminShutdownAnnotation,
k8s.ProxyLogFormatAnnotation,
k8s.ProxyLogLevelAnnotation,
k8s.ProxyLogHTTPHeaders,
k8s.ProxyMemoryLimitAnnotation,
k8s.ProxyMemoryRequestAnnotation,
k8s.ProxyEphemeralStorageLimitAnnotation,
k8s.ProxyEphemeralStorageRequestAnnotation,
k8s.ProxyUIDAnnotation,
k8s.ProxyGIDAnnotation,
k8s.ProxyVersionOverrideAnnotation,
k8s.ProxyRequireIdentityOnInboundPortsAnnotation,
k8s.ProxyIgnoreInboundPortsAnnotation,
k8s.ProxyOpaquePortsAnnotation,
k8s.ProxyIgnoreOutboundPortsAnnotation,
k8s.ProxyOutboundConnectTimeout,
k8s.ProxyInboundConnectTimeout,
k8s.ProxyAwait,
k8s.ProxyDefaultInboundPolicyAnnotation,
k8s.ProxySkipSubnetsAnnotation,
k8s.ProxyAccessLogAnnotation,
k8s.ProxyShutdownGracePeriodAnnotation,
k8s.ProxyOutboundDiscoveryCacheUnusedTimeout,
k8s.ProxyInboundDiscoveryCacheUnusedTimeout,
k8s.ProxyDisableOutboundProtocolDetectTimeout,
k8s.ProxyDisableInboundProtocolDetectTimeout,
}
// ProxyAlphaConfigAnnotations is the list of all alpha configuration
// (config.alpha prefix) that can be applied to a pod or namespace.
ProxyAlphaConfigAnnotations = []string{
k8s.ProxyWaitBeforeExitSecondsAnnotation,
k8s.ProxyEnableNativeSidecarAnnotation,
}
)
// Origin defines where the input YAML comes from. Refer the ResourceConfig's
// 'origin' field
type Origin int
const (
// OriginCLI is the value of the ResourceConfig's 'origin' field if the input
// YAML comes from the CLI
OriginCLI Origin = iota
// OriginWebhook is the value of the ResourceConfig's 'origin' field if the input
// YAML comes from the CLI
OriginWebhook
// OriginUnknown is the value of the ResourceConfig's 'origin' field if the
// input YAML comes from an unknown source
OriginUnknown
)
// OwnerRetrieverFunc is a function that returns a pod's owner reference
// kind and name
type OwnerRetrieverFunc func(*corev1.Pod) (string, string, error)
// ResourceConfig contains the parsed information for a given workload
type ResourceConfig struct {
// These values used for the rendering of the patch may be further
// overridden by the annotations on the resource or the resource's
// namespace.
values *l5dcharts.Values
namespace string
// These annotations from the resources's namespace are used as a base.
// The resources's annotations will be applied on top of these, which
// allows the nsAnnotations to act as a default.
nsAnnotations map[string]string
ownerRetriever OwnerRetrieverFunc
origin Origin
workload struct {
obj runtime.Object
metaType metav1.TypeMeta
// Meta is the workload's metadata. It's exported so that metadata of
// non-workload resources can be unmarshalled by the YAML parser
Meta *metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
ownerRef *metav1.OwnerReference
}
pod struct {
meta *metav1.ObjectMeta
// This fields hold labels and annotations which are to be added to the
// injected resource. This is different from meta.Labels and
// meta.Annotations which are the labels and annotations on the original
// resource before injection.
labels map[string]string
annotations map[string]string
spec *corev1.PodSpec
}
}
type podPatch struct {
l5dcharts.Values
PathPrefix string `json:"pathPrefix"`
AddRootMetadata bool `json:"addRootMetadata"`
AddRootAnnotations bool `json:"addRootAnnotations"`
Annotations map[string]string `json:"annotations"`
AddRootLabels bool `json:"addRootLabels"`
AddRootInitContainers bool `json:"addRootInitContainers"`
AddRootVolumes bool `json:"addRootVolumes"`
Labels map[string]string `json:"labels"`
DebugContainer *l5dcharts.DebugContainer `json:"debugContainer"`
}
type annotationPatch struct {
AddRootAnnotations bool
OpaquePorts string
}
// AppendNamespaceAnnotations allows pods to inherit config specific annotations
// from the namespace they belong to. If the namespace has a valid config key
// that the pod does not, then it is appended to the pod's template
func AppendNamespaceAnnotations(base map[string]string, nsAnn map[string]string, workloadAnn map[string]string) {
ann := append(ProxyAnnotations, ProxyAlphaConfigAnnotations...)
ann = append(ann, k8s.ProxyInjectAnnotation)
for _, key := range ann {
if _, found := nsAnn[key]; !found {
continue
}
if val, ok := GetConfigOverride(key, workloadAnn, nsAnn); ok {
base[key] = val
}
}
}
// GetOverriddenValues returns the final Values struct which is created
// by overriding annotated configuration on top of default Values
func GetOverriddenValues(values *l5dcharts.Values, overrides map[string]string, namedPorts map[string]int32) (*l5dcharts.Values, error) {
// Make a copy of Values and mutate that
copyValues, err := values.DeepCopy()
if err != nil {
return nil, err
}
applyAnnotationOverrides(copyValues, overrides, namedPorts)
return copyValues, nil
}
func applyAnnotationOverrides(values *l5dcharts.Values, annotations map[string]string, namedPorts map[string]int32) {
if override, ok := annotations[k8s.ProxyInjectAnnotation]; ok {
if override == k8s.ProxyInjectIngress {
values.Proxy.IsIngress = true
}
}
if override, ok := annotations[k8s.ProxyImageAnnotation]; ok {
values.Proxy.Image.Name = override
}
if override, ok := annotations[k8s.ProxyVersionOverrideAnnotation]; ok {
values.Proxy.Image.Version = override
}
if override, ok := annotations[k8s.ProxyImagePullPolicyAnnotation]; ok {
values.Proxy.Image.PullPolicy = override
}
if override, ok := annotations[k8s.ProxyInitImageVersionAnnotation]; ok {
values.ProxyInit.Image.Version = override
}
if override, ok := annotations[k8s.ProxyControlPortAnnotation]; ok {
controlPort, err := strconv.ParseInt(override, 10, 32)
if err == nil {
values.Proxy.Ports.Control = int32(controlPort)
}
}
if override, ok := annotations[k8s.ProxyInboundPortAnnotation]; ok {
inboundPort, err := strconv.ParseInt(override, 10, 32)
if err == nil {
values.Proxy.Ports.Inbound = int32(inboundPort)
}
}
if override, ok := annotations[k8s.ProxyAdminPortAnnotation]; ok {
adminPort, err := strconv.ParseInt(override, 10, 32)
if err == nil {
values.Proxy.Ports.Admin = int32(adminPort)
}
}
if override, ok := annotations[k8s.ProxyOutboundPortAnnotation]; ok {
outboundPort, err := strconv.ParseInt(override, 10, 32)
if err == nil {
values.Proxy.Ports.Outbound = int32(outboundPort)
}
}
if override, ok := annotations[k8s.ProxyPodInboundPortsAnnotation]; ok {
values.Proxy.PodInboundPorts = override
}
if override, ok := annotations[k8s.ProxyAdminShutdownAnnotation]; ok {
if override == k8s.Enabled || override == k8s.Disabled {
values.Proxy.EnableShutdownEndpoint = override == k8s.Enabled
} else {
log.Warnf("unrecognized value used for the %s annotation, valid values are: [%s, %s]", k8s.ProxyAdminShutdownAnnotation, k8s.Enabled, k8s.Disabled)
}
}
if override, ok := annotations[k8s.ProxyLogLevelAnnotation]; ok {
values.Proxy.LogLevel = override
}
if override, ok := annotations[k8s.ProxyLogHTTPHeaders]; ok {
values.Proxy.LogHTTPHeaders = override
}
if override, ok := annotations[k8s.ProxyLogFormatAnnotation]; ok {
values.Proxy.LogFormat = override
}
if override, ok := annotations[k8s.ProxyRequireIdentityOnInboundPortsAnnotation]; ok {
values.Proxy.RequireIdentityOnInboundPorts = override
}
if override, ok := annotations[k8s.ProxyOutboundConnectTimeout]; ok {
duration, err := time.ParseDuration(override)
if err != nil {
log.Warnf("unrecognized proxy-outbound-connect-timeout duration value found on pod annotation: %s", err.Error())
} else {
values.Proxy.OutboundConnectTimeout = fmt.Sprintf("%dms", int(duration.Seconds()*1000))
}
}
if override, ok := annotations[k8s.ProxyInboundConnectTimeout]; ok {
duration, err := time.ParseDuration(override)
if err != nil {
log.Warnf("unrecognized proxy-inbound-connect-timeout duration value found on pod annotation: %s", err.Error())
} else {
values.Proxy.InboundConnectTimeout = fmt.Sprintf("%dms", int(duration.Seconds()*1000))
}
}
if override, ok := annotations[k8s.ProxyOutboundDiscoveryCacheUnusedTimeout]; ok {
duration, err := time.ParseDuration(override)
if err != nil {
log.Warnf("unrecognized duration value used on pod annotation %s: %s", k8s.ProxyOutboundDiscoveryCacheUnusedTimeout, err.Error())
} else {
values.Proxy.OutboundDiscoveryCacheUnusedTimeout = fmt.Sprintf("%ds", int(duration.Seconds()))
}
}
if override, ok := annotations[k8s.ProxyInboundDiscoveryCacheUnusedTimeout]; ok {
duration, err := time.ParseDuration(override)
if err != nil {
log.Warnf("unrecognized duration value used on pod annotation %s: %s", k8s.ProxyInboundDiscoveryCacheUnusedTimeout, err.Error())
} else {
values.Proxy.InboundDiscoveryCacheUnusedTimeout = fmt.Sprintf("%ds", int(duration.Seconds()))
}
}
if override, ok := annotations[k8s.ProxyDisableOutboundProtocolDetectTimeout]; ok {
value, err := strconv.ParseBool(override)
if err == nil {
values.Proxy.DisableOutboundProtocolDetectTimeout = value
} else {
log.Warnf("unrecognised value used on pod annotation %s: %s", k8s.ProxyDisableOutboundProtocolDetectTimeout, err.Error())
}
}
if override, ok := annotations[k8s.ProxyDisableInboundProtocolDetectTimeout]; ok {
value, err := strconv.ParseBool(override)
if err == nil {
values.Proxy.DisableInboundProtocolDetectTimeout = value
} else {
log.Warnf("unrecognised value used on pod annotation %s: %s", k8s.ProxyDisableInboundProtocolDetectTimeout, err.Error())
}
}
if override, ok := annotations[k8s.ProxyShutdownGracePeriodAnnotation]; ok {
duration, err := time.ParseDuration(override)
if err != nil {
log.Warnf("unrecognized proxy-shutdown-grace-period duration value found on pod annotation: %s", err.Error())
} else {
values.Proxy.ShutdownGracePeriod = fmt.Sprintf("%dms", int(duration.Seconds()*1000))
}
}
if override, ok := annotations[k8s.ProxyEnableGatewayAnnotation]; ok {
value, err := strconv.ParseBool(override)
if err == nil {
values.Proxy.IsGateway = value
}
}
if override, ok := annotations[k8s.ProxyWaitBeforeExitSecondsAnnotation]; ok {
waitBeforeExitSeconds, err := strconv.ParseUint(override, 10, 64)
if nil != err {
log.Warnf("unrecognized value used for the %s annotation, uint64 is expected: %s",
k8s.ProxyWaitBeforeExitSecondsAnnotation, override)
} else {
values.Proxy.WaitBeforeExitSeconds = waitBeforeExitSeconds
}
}
if override, ok := annotations[k8s.ProxyEnableNativeSidecarAnnotation]; ok {
value, err := strconv.ParseBool(override)
if err == nil {
values.Proxy.NativeSidecar = value
}
}
if override, ok := annotations[k8s.ProxyCPURequestAnnotation]; ok {
_, err := k8sResource.ParseQuantity(override)
if err != nil {
log.Warnf("%s (%s)", err, k8s.ProxyCPURequestAnnotation)
} else {
values.Proxy.Resources.CPU.Request = override
}
}
if override, ok := annotations[k8s.ProxyMemoryRequestAnnotation]; ok {
_, err := k8sResource.ParseQuantity(override)
if err != nil {
log.Warnf("%s (%s)", err, k8s.ProxyMemoryRequestAnnotation)
} else {
values.Proxy.Resources.Memory.Request = override
}
}
if override, ok := annotations[k8s.ProxyEphemeralStorageRequestAnnotation]; ok {
_, err := k8sResource.ParseQuantity(override)
if err != nil {
log.Warnf("%s (%s)", err, k8s.ProxyEphemeralStorageRequestAnnotation)
} else {
values.Proxy.Resources.EphemeralStorage.Request = override
}
}
if override, ok := annotations[k8s.ProxyCPULimitAnnotation]; ok {
q, err := k8sResource.ParseQuantity(override)
if err != nil {
log.Warnf("%s (%s)", err, k8s.ProxyCPULimitAnnotation)
} else {
values.Proxy.Resources.CPU.Limit = override
n, err := ToWholeCPUCores(q)
if err != nil {
log.Warnf("%s (%s)", err, k8s.ProxyCPULimitAnnotation)
}
values.Proxy.Cores = n
}
}
if override, ok := annotations[k8s.ProxyMemoryLimitAnnotation]; ok {
_, err := k8sResource.ParseQuantity(override)
if err != nil {
log.Warnf("%s (%s)", err, k8s.ProxyMemoryLimitAnnotation)
} else {
values.Proxy.Resources.Memory.Limit = override
}
}
if override, ok := annotations[k8s.ProxyEphemeralStorageLimitAnnotation]; ok {
_, err := k8sResource.ParseQuantity(override)
if err != nil {
log.Warnf("%s (%s)", err, k8s.ProxyEphemeralStorageLimitAnnotation)
} else {
values.Proxy.Resources.EphemeralStorage.Limit = override
}
}
if override, ok := annotations[k8s.ProxyUIDAnnotation]; ok {
v, err := strconv.ParseInt(override, 10, 64)
if err == nil {
values.Proxy.UID = v
}
}
if override, ok := annotations[k8s.ProxyGIDAnnotation]; ok {
v, err := strconv.ParseInt(override, 10, 64)
if err == nil {
values.Proxy.GID = v
}
}
if override, ok := annotations[k8s.ProxyEnableExternalProfilesAnnotation]; ok {
value, err := strconv.ParseBool(override)
if err == nil {
values.Proxy.EnableExternalProfiles = value
}
}
if override, ok := annotations[k8s.ProxyInitImageAnnotation]; ok {
values.ProxyInit.Image.Name = override
}
if override, ok := annotations[k8s.ProxyImagePullPolicyAnnotation]; ok {
values.ProxyInit.Image.PullPolicy = override
}
if override, ok := annotations[k8s.ProxyIgnoreInboundPortsAnnotation]; ok {
values.ProxyInit.IgnoreInboundPorts = override
}
if override, ok := annotations[k8s.ProxyIgnoreOutboundPortsAnnotation]; ok {
values.ProxyInit.IgnoreOutboundPorts = override
}
if override, ok := annotations[k8s.ProxyOpaquePortsAnnotation]; ok {
var opaquePorts strings.Builder
for _, pr := range util.ParseContainerOpaquePorts(override, namedPorts) {
if opaquePorts.Len() > 0 {
opaquePorts.WriteRune(',')
}
opaquePorts.WriteString(pr.ToString())
}
values.Proxy.OpaquePorts = opaquePorts.String()
}
if override, ok := annotations[k8s.DebugImageAnnotation]; ok {
values.DebugContainer.Image.Name = override
}
if override, ok := annotations[k8s.DebugImageVersionAnnotation]; ok {
values.DebugContainer.Image.Version = override
}
if override, ok := annotations[k8s.DebugImagePullPolicyAnnotation]; ok {
values.DebugContainer.Image.PullPolicy = override
}
if override, ok := annotations[k8s.ProxyAwait]; ok {
if override == k8s.Enabled || override == k8s.Disabled {
values.Proxy.Await = override == k8s.Enabled
} else {
log.Warnf("unrecognized value used for the %s annotation, valid values are: [%s, %s]", k8s.ProxyAwait, k8s.Enabled, k8s.Disabled)
}
}
if override, ok := annotations[k8s.ProxyDefaultInboundPolicyAnnotation]; ok {
if override != k8s.AllUnauthenticated && override != k8s.AllAuthenticated && override != k8s.ClusterUnauthenticated && override != k8s.ClusterAuthenticated && override != k8s.Deny {
log.Warnf("unrecognized value used for the %s annotation, valid values are: [%s, %s, %s, %s, %s]", k8s.ProxyDefaultInboundPolicyAnnotation, k8s.AllUnauthenticated, k8s.AllAuthenticated, k8s.ClusterUnauthenticated, k8s.ClusterAuthenticated, k8s.Deny)
} else {
values.Proxy.DefaultInboundPolicy = override
}
}
if override, ok := annotations[k8s.ProxySkipSubnetsAnnotation]; ok {
values.ProxyInit.SkipSubnets = override
}
if override, ok := annotations[k8s.ProxyAccessLogAnnotation]; ok {
values.Proxy.AccessLog = override
}
}
// NewResourceConfig creates and initializes a ResourceConfig
func NewResourceConfig(values *l5dcharts.Values, origin Origin, ns string) *ResourceConfig {
config := &ResourceConfig{
namespace: ns,
nsAnnotations: make(map[string]string),
values: values,
origin: origin,
}
config.workload.Meta = &metav1.ObjectMeta{}
config.pod.meta = &metav1.ObjectMeta{}
config.pod.labels = map[string]string{k8s.ControllerNSLabel: ns}
config.pod.annotations = map[string]string{}
return config
}
// WithKind enriches ResourceConfig with the workload kind
func (conf *ResourceConfig) WithKind(kind string) *ResourceConfig {
conf.workload.metaType = metav1.TypeMeta{Kind: kind}
return conf
}
// WithNsAnnotations enriches ResourceConfig with the namespace annotations, that can
// be used in shouldInject()
func (conf *ResourceConfig) WithNsAnnotations(m map[string]string) *ResourceConfig {
conf.nsAnnotations = m
return conf
}
// WithOwnerRetriever enriches ResourceConfig with a function that allows to retrieve
// the kind and name of the workload's owner reference
func (conf *ResourceConfig) WithOwnerRetriever(f OwnerRetrieverFunc) *ResourceConfig {
conf.ownerRetriever = f
return conf
}
// GetOwnerRef returns a reference to the resource's owner resource, if any
func (conf *ResourceConfig) GetOwnerRef() *metav1.OwnerReference {
return conf.workload.ownerRef
}
func (conf *ResourceConfig) GetOverrideAnnotations() map[string]string {
return conf.pod.annotations
}
func (conf *ResourceConfig) GetNsAnnotations() map[string]string {
return conf.nsAnnotations
}
func (conf *ResourceConfig) GetWorkloadAnnotations() map[string]string {
if conf.IsPod() {
return conf.pod.meta.Annotations
}
return conf.workload.Meta.Annotations
}
// AppendPodAnnotations appends the given annotations to the pod spec in conf
func (conf *ResourceConfig) AppendPodAnnotations(annotations map[string]string) {
for annotation, value := range annotations {
conf.pod.annotations[annotation] = value
}
}
// AppendPodAnnotation appends the given single annotation to the pod spec in conf
func (conf *ResourceConfig) AppendPodAnnotation(k, v string) {
conf.pod.annotations[k] = v
}
// YamlMarshalObj returns the yaml for the workload in conf
func (conf *ResourceConfig) YamlMarshalObj() ([]byte, error) {
j, err := getFilteredJSON(conf.workload.obj)
if err != nil {
return nil, err
}
return yaml.JSONToYAML(j)
}
// ParseMetaAndYAML extracts the workload metadata and pod specs from the given
// input bytes. The results are stored in the conf's fields.
func (conf *ResourceConfig) ParseMetaAndYAML(bytes []byte) (*Report, error) {
if err := conf.parse(bytes); err != nil {
return nil, err
}
return newReport(conf), nil
}
// FromObject extracts the workload metadata and pod specs from the given
// runtime.Object instance. The results are stored in the conf's fields.
func (conf *ResourceConfig) FromObject(v runtime.Object) (*Report, error) {
if err := conf.populateMeta(v); err != nil {
return nil, err
}
return newReport(conf), nil
}
// GetValues returns the values used for rendering patches.
func (conf *ResourceConfig) GetValues() *l5dcharts.Values {
return conf.values
}
func (conf *ResourceConfig) getAnnotationOverrides() map[string]string {
overrides := map[string]string{}
for k, v := range conf.pod.meta.Annotations {
overrides[k] = v
}
if conf.origin != OriginCLI {
for k, v := range conf.pod.annotations {
overrides[k] = v
}
}
return overrides
}
// GetPodPatch returns the JSON patch containing the proxy and init containers specs, if any.
// If injectProxy is false, only the config.linkerd.io annotations are set.
func (conf *ResourceConfig) GetPodPatch(injectProxy bool) ([]byte, error) {
namedPorts := make(map[string]int32)
if conf.HasPodTemplate() {
namedPorts = util.GetNamedPorts(conf.pod.spec.Containers)
}
values, err := GetOverriddenValues(conf.values, conf.getAnnotationOverrides(), namedPorts)
values.Proxy.PodInboundPorts = getPodInboundPorts(conf.pod.spec)
if err != nil {
return nil, fmt.Errorf("could not generate Overridden Values: %w", err)
}
if values.ClusterNetworks != "" {
for _, network := range strings.Split(strings.Trim(values.ClusterNetworks, ","), ",") {
if _, _, err := net.ParseCIDR(network); err != nil {
return nil, fmt.Errorf("cannot parse destination get networks: %w", err)
}
}
}
patch := &podPatch{
Values: *values,
Annotations: map[string]string{},
Labels: map[string]string{},
}
switch strings.ToLower(conf.workload.metaType.Kind) {
case k8s.Pod:
case k8s.CronJob:
patch.PathPrefix = "/spec/jobTemplate/spec/template"
default:
patch.PathPrefix = "/spec/template"
}
if conf.pod.spec != nil {
conf.injectPodAnnotations(patch)
if injectProxy {
conf.injectObjectMeta(patch)
conf.injectPodSpec(patch)
} else {
patch.Proxy = nil
patch.ProxyInit = nil
}
}
rawValues, err := yaml.Marshal(patch)
if err != nil {
return nil, err
}
files := []*loader.BufferedFile{
{Name: chartutil.ChartfileName},
{Name: "requirements.yaml"},
{Name: "templates/patch.json"},
}
chart := &charts.Chart{
Name: "patch",
Dir: "patch",
Namespace: conf.namespace,
RawValues: rawValues,
Files: files,
Fs: static.Templates,
}
buf, err := chart.Render()
if err != nil {
return nil, err
}
// Get rid of invalid trailing commas
res := rTrail.ReplaceAll(buf.Bytes(), []byte("}\n]"))
return res, nil
}
// GetConfigAnnotation returns two values. The first value is the annotation
// value for a given key. The second is used to decide whether or not the caller
// should add the annotation. The caller should not add the annotation if the
// resource already has its own.
func GetConfigOverride(annotationKey string, workloadAnn map[string]string, nsAnn map[string]string) (string, bool) {
_, ok := workloadAnn[annotationKey]
if ok {
log.Debugf("using workload %s annotation value", annotationKey)
return "", false
}
annotation, ok := nsAnn[annotationKey]
if ok {
log.Debugf("using namespace %s annotation value", annotationKey)
return annotation, true
}
return "", false
}
// CreateOpaquePortsPatch creates a patch that will add the default
// list of opaque ports.
func (conf *ResourceConfig) CreateOpaquePortsPatch() ([]byte, error) {
if conf.HasWorkloadAnnotation(k8s.ProxyOpaquePortsAnnotation) {
// The workload already has the opaque ports annotation so a patch
// does not need to be created.
return nil, nil
}
workloadAnn := conf.workload.Meta.Annotations
if conf.IsPod() {
workloadAnn = conf.pod.meta.Annotations
}
opaquePorts, ok := GetConfigOverride(k8s.ProxyOpaquePortsAnnotation, workloadAnn, conf.nsAnnotations)
if ok {
// The workload's namespace has the opaque ports annotation, so it
// should inherit that value. A patch is created which adds that
// list.
return conf.CreateAnnotationPatch(opaquePorts)
}
// Both the workload and the namespace do not have the annotation so a
// patch is created which adds the default list.
defaultPorts := strings.Split(conf.GetValues().Proxy.OpaquePorts, ",")
var filteredPorts []string
if conf.IsPod() {
// The workload is a pod so only add the default opaque ports that it
// exposes as container ports.
filteredPorts = conf.FilterPodOpaquePorts(defaultPorts)
} else if conf.IsService() {
// The workload is a service so only add the default opaque ports that
// are exposed as a service port, or targeted as a targetPort.
service := conf.workload.obj.(*corev1.Service)
for _, p := range service.Spec.Ports {
port := strconv.Itoa(int(p.Port))
if p.TargetPort.Type == 0 && p.TargetPort.IntVal == 0 {
// The port's targetPort is not set, so add the port if is
// opaque by default. Checking that targetPort is not set
// avoids marking a port as opaque if it targets a port that
// not opaque (e.g. port=3306 and targetPort=80; 3306 should
// not be opaque)
if util.ContainsString(port, defaultPorts) {
filteredPorts = append(filteredPorts, port)
}
} else if util.ContainsString(strconv.Itoa(int(p.TargetPort.IntVal)), defaultPorts) {
// The port's targetPort is set; if it is opaque then port
// should also be opaque.
filteredPorts = append(filteredPorts, port)
}
}
}
if len(filteredPorts) == 0 {
// There are no default opaque ports to add so a patch does not need
// to be created.
return nil, nil
}
ports := strings.Join(filteredPorts, ",")
return conf.CreateAnnotationPatch(ports)
}
// FilterPodOpaquePorts returns a list of opaque ports that a pod exposes that
// are also in the given default opaque ports list.
func (conf *ResourceConfig) FilterPodOpaquePorts(defaultPorts []string) []string {
var filteredPorts []string
for _, c := range conf.pod.spec.Containers {
for _, p := range c.Ports {
port := strconv.Itoa(int(p.ContainerPort))
if util.ContainsString(port, defaultPorts) {
filteredPorts = append(filteredPorts, port)
}
}
}
return filteredPorts
}
// HasWorkloadAnnotation returns true if the workload has the annotation set
// by the resource config or its metadata.
func (conf *ResourceConfig) HasWorkloadAnnotation(annotation string) bool {
if _, ok := conf.pod.meta.Annotations[annotation]; ok {
return true
}
if _, ok := conf.workload.Meta.Annotations[annotation]; ok {
return true
}
_, ok := conf.pod.annotations[annotation]
return ok
}
// CreateAnnotationPatch returns a json patch which adds the opaque ports
// annotation with the `opaquePorts` value.
func (conf *ResourceConfig) CreateAnnotationPatch(opaquePorts string) ([]byte, error) {
addRootAnnotations := false
if conf.IsPod() {
addRootAnnotations = len(conf.pod.meta.Annotations) == 0
} else {
addRootAnnotations = len(conf.workload.Meta.Annotations) == 0
}
patch := &annotationPatch{
AddRootAnnotations: addRootAnnotations,
OpaquePorts: opaquePorts,
}
t, err := template.New("tpl").Parse(tpl)
if err != nil {
return nil, err
}
var patchJSON bytes.Buffer
if err = t.Execute(&patchJSON, patch); err != nil {
return nil, err
}
return patchJSON.Bytes(), nil
}
// Note this switch also defines what kinds are injectable
func (conf *ResourceConfig) getFreshWorkloadObj() runtime.Object {
switch strings.ToLower(conf.workload.metaType.Kind) {
case k8s.Deployment:
return &appsv1.Deployment{}
case k8s.ReplicationController:
return &corev1.ReplicationController{}
case k8s.ReplicaSet:
return &appsv1.ReplicaSet{}
case k8s.Job:
return &batchv1.Job{}
case k8s.DaemonSet:
return &appsv1.DaemonSet{}
case k8s.StatefulSet:
return &appsv1.StatefulSet{}
case k8s.Pod:
return &corev1.Pod{}
case k8s.Namespace:
return &corev1.Namespace{}
case k8s.CronJob:
return &batchv1.CronJob{}
case k8s.Service:
return &corev1.Service{}
}
return nil
}
// JSONToYAML is a replacement for the same function in sigs.k8s.io/yaml
// that does conserve the field order as portrayed in k8s' api structs
func (conf *ResourceConfig) JSONToYAML(bytes []byte) ([]byte, error) {
obj := conf.getFreshWorkloadObj()
if err := json.Unmarshal(bytes, obj); err != nil {
return nil, err
}
j, err := getFilteredJSON(obj)
if err != nil {
return nil, err
}
return yaml.JSONToYAML(j)
}
func (conf *ResourceConfig) populateMeta(obj runtime.Object) error {
switch v := obj.(type) {
case *appsv1.Deployment:
conf.workload.obj = v
conf.workload.Meta = &v.ObjectMeta
conf.pod.labels[k8s.ProxyDeploymentLabel] = v.Name
conf.pod.labels[k8s.WorkloadNamespaceLabel] = v.Namespace
conf.complete(&v.Spec.Template)
case *corev1.ReplicationController:
conf.workload.obj = v
conf.workload.Meta = &v.ObjectMeta
conf.pod.labels[k8s.ProxyReplicationControllerLabel] = v.Name
conf.pod.labels[k8s.WorkloadNamespaceLabel] = v.Namespace
conf.complete(v.Spec.Template)
case *appsv1.ReplicaSet:
conf.workload.obj = v
conf.workload.Meta = &v.ObjectMeta
conf.pod.labels[k8s.ProxyReplicaSetLabel] = v.Name
conf.pod.labels[k8s.WorkloadNamespaceLabel] = v.Namespace
conf.complete(&v.Spec.Template)
case *batchv1.Job:
conf.workload.obj = v
conf.workload.Meta = &v.ObjectMeta
conf.pod.labels[k8s.ProxyJobLabel] = v.Name
conf.pod.labels[k8s.WorkloadNamespaceLabel] = v.Namespace
conf.complete(&v.Spec.Template)
case *appsv1.DaemonSet:
conf.workload.obj = v
conf.workload.Meta = &v.ObjectMeta
conf.pod.labels[k8s.ProxyDaemonSetLabel] = v.Name
conf.pod.labels[k8s.WorkloadNamespaceLabel] = v.Namespace
conf.complete(&v.Spec.Template)
case *appsv1.StatefulSet:
conf.workload.obj = v
conf.workload.Meta = &v.ObjectMeta
conf.pod.labels[k8s.ProxyStatefulSetLabel] = v.Name
conf.pod.labels[k8s.WorkloadNamespaceLabel] = v.Namespace
conf.complete(&v.Spec.Template)
case *corev1.Namespace:
conf.workload.obj = v
conf.workload.Meta = &v.ObjectMeta
if conf.workload.Meta.Annotations == nil {
conf.workload.Meta.Annotations = map[string]string{}
}
case *batchv1.CronJob:
conf.workload.obj = v
conf.workload.Meta = &v.ObjectMeta
conf.pod.labels[k8s.ProxyCronJobLabel] = v.Name
conf.pod.labels[k8s.WorkloadNamespaceLabel] = v.Namespace
conf.complete(&v.Spec.JobTemplate.Spec.Template)
case *corev1.Pod:
conf.workload.obj = v
conf.pod.spec = &v.Spec
conf.pod.meta = &v.ObjectMeta
if conf.ownerRetriever != nil {
kind, name, err := conf.ownerRetriever(v)
if err != nil {
return err
}
conf.workload.ownerRef = &metav1.OwnerReference{Kind: kind, Name: name}
switch kind {
case k8s.Deployment:
conf.pod.labels[k8s.ProxyDeploymentLabel] = name
case k8s.ReplicationController:
conf.pod.labels[k8s.ProxyReplicationControllerLabel] = name
case k8s.ReplicaSet:
conf.pod.labels[k8s.ProxyReplicaSetLabel] = name
case k8s.Job:
conf.pod.labels[k8s.ProxyJobLabel] = name
case k8s.DaemonSet:
conf.pod.labels[k8s.ProxyDaemonSetLabel] = name
case k8s.StatefulSet:
conf.pod.labels[k8s.ProxyStatefulSetLabel] = name
}
}
conf.pod.labels[k8s.WorkloadNamespaceLabel] = v.Namespace
if conf.pod.meta.Annotations == nil {
conf.pod.meta.Annotations = map[string]string{}
}
case *corev1.Service:
conf.workload.obj = v
conf.workload.Meta = &v.ObjectMeta
if conf.workload.Meta.Annotations == nil {
conf.workload.Meta.Annotations = map[string]string{}
}
default:
return fmt.Errorf("unsupported type %T", v)
}
return nil
}
// parse parses the bytes payload, filling the gaps in ResourceConfig
// depending on the workload kind
func (conf *ResourceConfig) parse(bytes []byte) error {
// The Kubernetes API is versioned and each version has an API modeled
// with its own distinct Go types. If we tell `yaml.Unmarshal()` which
// version we support then it will provide a representation of that
// object using the given type if possible. However, it only allows us
// to supply one object (of one type), so first we have to determine
// what kind of object `bytes` represents so we can pass an object of
// the correct type to `yaml.Unmarshal()`.
// ---------------------------------------
// Note: bytes is expected to be YAML and will only modify it when a
// supported type is found. Otherwise, conf is left unmodified.
// When injecting the linkerd proxy into a linkerd controller pod. The linkerd proxy's
// LINKERD2_PROXY_DESTINATION_SVC_ADDR variable must be set to localhost for
// the following reasons:
// 1. According to https://github.com/kubernetes/minikube/issues/1568, minikube has an issue
// where pods are unable to connect to themselves through their associated service IP.
// Setting the LINKERD2_PROXY_DESTINATION_SVC_ADDR to localhost allows the
// proxy to bypass kube DNS name resolution as a workaround to this issue.
// 2. We avoid the TLS overhead in encrypting and decrypting intra-pod traffic i.e. traffic
// between containers in the same pod.
// 3. Using a Service IP instead of localhost would mean intra-pod traffic would be load-balanced
// across all controller pod replicas. This is undesirable as we would want all traffic between
// containers to be self contained.
// 4. We skip recording telemetry for intra-pod traffic within the control plane.
if err := yaml.Unmarshal(bytes, &conf.workload.metaType); err != nil {
return err
}
obj := conf.getFreshWorkloadObj()
switch v := obj.(type) {
case *appsv1.Deployment,
*corev1.ReplicationController,
*appsv1.ReplicaSet,
*batchv1.Job,
*appsv1.DaemonSet,
*appsv1.StatefulSet,
*corev1.Namespace,
*batchv1.CronJob,
*corev1.Pod,
*corev1.Service:
if err := yaml.Unmarshal(bytes, v); err != nil {
return err
}
if err := conf.populateMeta(v); err != nil {
return err
}
default:
// unmarshal the metadata of other resource kinds like namespace, secret,
// config map etc. to be used in the report struct
if err := yaml.Unmarshal(bytes, &conf.workload); err != nil {
return err
}
}
return nil
}
func (conf *ResourceConfig) complete(template *corev1.PodTemplateSpec) {
conf.pod.spec = &template.Spec
conf.pod.meta = &template.ObjectMeta
if conf.pod.meta.Annotations == nil {
conf.pod.meta.Annotations = map[string]string{}
}
}
// injectPodSpec adds linkerd sidecars to the provided PodSpec.
func (conf *ResourceConfig) injectPodSpec(values *podPatch) {
saVolumeMount := conf.serviceAccountVolumeMount()
// use the primary container's capabilities to ensure psp compliance, if
// enabled
if conf.pod.spec.Containers != nil && len(conf.pod.spec.Containers) > 0 {
if sc := conf.pod.spec.Containers[0].SecurityContext; sc != nil && sc.Capabilities != nil {
values.Proxy.Capabilities = &l5dcharts.Capabilities{
Add: []string{},
Drop: []string{},
}
for _, add := range sc.Capabilities.Add {
values.Proxy.Capabilities.Add = append(values.Proxy.Capabilities.Add, string(add))
}
for _, drop := range sc.Capabilities.Drop {
values.Proxy.Capabilities.Drop = append(values.Proxy.Capabilities.Drop, string(drop))
}
}
}
if saVolumeMount != nil {
values.Proxy.SAMountPath = &l5dcharts.VolumeMountPath{
Name: saVolumeMount.Name,
MountPath: saVolumeMount.MountPath,
ReadOnly: saVolumeMount.ReadOnly,
}
}
if v := conf.pod.meta.Annotations[k8s.ProxyEnableDebugAnnotation]; v != "" {
debug, err := strconv.ParseBool(v)
if err != nil {
log.Warnf("unrecognized value used for the %s annotation: %s", k8s.ProxyEnableDebugAnnotation, v)
debug = false
}
if debug {
log.Infof("inject debug container")
values.DebugContainer = &l5dcharts.DebugContainer{
Image: &l5dcharts.Image{
Name: conf.values.DebugContainer.Image.Name,
Version: conf.values.DebugContainer.Image.Version,
PullPolicy: conf.values.DebugContainer.Image.PullPolicy,
},
}
}
}
conf.injectProxyInit(values)
values.AddRootVolumes = len(conf.pod.spec.Volumes) == 0
}
func (conf *ResourceConfig) injectProxyInit(values *podPatch) {
// Fill common fields from Proxy into ProxyInit
if values.Proxy.Capabilities != nil {
values.ProxyInit.Capabilities = &l5dcharts.Capabilities{}
values.ProxyInit.Capabilities.Add = values.Proxy.Capabilities.Add
values.ProxyInit.Capabilities.Drop = []string{}
for _, drop := range values.Proxy.Capabilities.Drop {
// Skip NET_RAW and NET_ADMIN as the init container requires them to setup iptables.
if drop == "NET_RAW" || drop == "NET_ADMIN" {
continue
}
values.ProxyInit.Capabilities.Drop = append(values.ProxyInit.Capabilities.Drop, drop)
}
}
values.ProxyInit.SAMountPath = values.Proxy.SAMountPath
if v := conf.pod.meta.Annotations[k8s.CloseWaitTimeoutAnnotation]; v != "" {
closeWait, err := time.ParseDuration(v)
if err != nil {
log.Warnf("invalid duration value used for the %s annotation: %s", k8s.CloseWaitTimeoutAnnotation, v)
} else {
values.ProxyInit.CloseWaitTimeoutSecs = int64(closeWait.Seconds())
}
}
values.AddRootInitContainers = len(conf.pod.spec.InitContainers) == 0
}
func (conf *ResourceConfig) serviceAccountVolumeMount() *corev1.VolumeMount {
// Probably always true, but want to be super-safe
if containers := conf.pod.spec.Containers; len(containers) > 0 {
for _, vm := range containers[0].VolumeMounts {
if vm.MountPath == k8s.MountPathServiceAccount {
vm := vm // pin
return &vm
}
}
}
return nil
}
// Given a ObjectMeta, update ObjectMeta in place with the new labels and
// annotations.
func (conf *ResourceConfig) injectObjectMeta(values *podPatch) {
// Default proxy version to linkerd version
if values.Proxy.Image.Version != "" {
values.Annotations[k8s.ProxyVersionAnnotation] = values.Proxy.Image.Version
} else {
values.Annotations[k8s.ProxyVersionAnnotation] = values.LinkerdVersion
}
// Add the cert bundle's checksum to the workload's annotations.
checksumBytes := sha256.Sum256([]byte(values.IdentityTrustAnchorsPEM))
checksum := hex.EncodeToString(checksumBytes[:])
values.Annotations[k8s.ProxyTrustRootSHA] = checksum
if len(conf.pod.labels) > 0 {
values.AddRootLabels = len(conf.pod.meta.Labels) == 0
for _, k := range sortedKeys(conf.pod.labels) {
values.Labels[k] = conf.pod.labels[k]
}
}
}
func (conf *ResourceConfig) injectPodAnnotations(values *podPatch) {
// ObjectMetaAnnotations.Annotations is nil for new empty structs, but we always initialize
// it to an empty map in parse() above, so we follow suit here.
emptyMeta := &metav1.ObjectMeta{Annotations: map[string]string{}}
// Cronjobs might have an empty `spec.jobTemplate.spec.template.metadata`
// field so we make sure to create it if needed, before attempting adding annotations
values.AddRootMetadata = reflect.DeepEqual(conf.pod.meta, emptyMeta)
values.AddRootAnnotations = len(conf.pod.meta.Annotations) == 0
for _, k := range sortedKeys(conf.pod.annotations) {
values.Annotations[k] = conf.pod.annotations[k]
// append any additional pod annotations to the pod's meta.
// for e.g., annotations that were converted from CLI inject options.
conf.pod.meta.Annotations[k] = conf.pod.annotations[k]
}
}
// GetOverriddenConfiguration returns a map of the overridden proxy annotations
func (conf *ResourceConfig) GetOverriddenConfiguration() map[string]string {
proxyOverrideConfig := map[string]string{}
for _, annotation := range ProxyAnnotations {
proxyOverrideConfig[annotation] = conf.pod.meta.Annotations[annotation]
}
return proxyOverrideConfig
}
// IsControlPlaneComponent returns true if the component is part of linkerd control plane
func (conf *ResourceConfig) IsControlPlaneComponent() bool {
_, b := conf.pod.meta.Labels[k8s.ControllerComponentLabel]
return b
}
func sortedKeys(m map[string]string) []string {
keys := []string{}
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
return keys
}
// IsNamespace checks if a given config is a workload of Kind namespace
func (conf *ResourceConfig) IsNamespace() bool {
return strings.ToLower(conf.workload.metaType.Kind) == k8s.Namespace
}
// IsService checks if a given config is a workload of Kind service
func (conf *ResourceConfig) IsService() bool {
return strings.ToLower(conf.workload.metaType.Kind) == k8s.Service
}
// IsPod checks if a given config is a workload of Kind pod.
func (conf *ResourceConfig) IsPod() bool {
return strings.ToLower(conf.workload.metaType.Kind) == k8s.Pod
}
// HasPodTemplate checks if a given config has a pod template spec.
func (conf *ResourceConfig) HasPodTemplate() bool {
return conf.pod.meta != nil && conf.pod.spec != nil
}
// AnnotateNamespace annotates a namespace resource config with `annotations`.
func (conf *ResourceConfig) AnnotateNamespace(annotations map[string]string) ([]byte, error) {
ns, ok := conf.workload.obj.(*corev1.Namespace)
if !ok {
return nil, errors.New("can't inject namespace. Type assertion failed")
}
ns.Annotations[k8s.ProxyInjectAnnotation] = k8s.ProxyInjectEnabled
if len(annotations) > 0 {
for annotation, value := range annotations {
ns.Annotations[annotation] = value
}
}
j, err := getFilteredJSON(ns)
if err != nil {
return nil, err
}
return yaml.JSONToYAML(j)
}
// AnnotateService annotates a service resource config with `annotations`.
func (conf *ResourceConfig) AnnotateService(annotations map[string]string) ([]byte, error) {
service, ok := conf.workload.obj.(*corev1.Service)
if !ok {
return nil, errors.New("can't inject service. Type assertion failed")
}
if len(annotations) > 0 {
for annotation, value := range annotations {
service.Annotations[annotation] = value
}
}
j, err := getFilteredJSON(service)
if err != nil {
return nil, err
}
return yaml.JSONToYAML(j)
}
// getFilteredJSON method performs JSON marshaling such that zero values of
// empty structs are respected by `omitempty` tags. We make use of a drop-in
// replacement of the standard json/encoding library, without which empty struct values
// present in workload objects would make it into the marshaled JSON.
func getFilteredJSON(conf runtime.Object) ([]byte, error) {
return jsonfilter.Marshal(&conf)
}
// ToWholeCPUCores coerces a k8s resource value to a whole integer value, rounding up.
func ToWholeCPUCores(q k8sResource.Quantity) (int64, error) {
q.RoundUp(0)
if n, ok := q.AsInt64(); ok {
return n, nil
}
return 0, fmt.Errorf("Could not parse cores: %s", q.String())
}
// getPodInboundPorts will return a string-formatted list of ports (in ascending
// order) based on a PodSpec object. The function will check each container in
// the pod and extract any defined ports. Additionally, it will also extract any
// healthcheck target probes, provided the probe is an HTTP healthcheck
func getPodInboundPorts(podSpec *corev1.PodSpec) string {
ports := make(map[int32]struct{})
if podSpec != nil {
for _, container := range podSpec.Containers {
for _, port := range container.Ports {
ports[port.ContainerPort] = struct{}{}
}
if readiness := container.ReadinessProbe; readiness != nil {
if port, ok := getProbePort(readiness); ok {
ports[port] = struct{}{}
}
}
if liveness := container.LivenessProbe; liveness != nil {
if port, ok := getProbePort(liveness); ok {
ports[port] = struct{}{}
}
}
}
}
portList := make([]string, 0, len(ports))
for port := range ports {
portList = append(portList, strconv.Itoa(int(port)))
}
// sort slice in ascending order
sort.Strings(portList)
return strings.Join(portList, ",")
}
// getProbePort takes the healthcheck probe spec of a container and returns the
// target port if the probe is configured to do an HTTPGet. The function returns
// the probe's target port and a success value (if successful)
func getProbePort(probe *corev1.Probe) (int32, bool) {
if probe.HTTPGet != nil {
// HTTPGet probes use a named port, in this case, do not return it. A
// named port must be declared in the container's own ports; if probe uses
// a named port it is likely the port has been seen before.
switch probe.HTTPGet.Port.Type {
case intstr.Int:
return probe.HTTPGet.Port.IntVal, true
}
}
return 0, false
}
package inject
import (
l5dcharts "github.com/linkerd/linkerd2/pkg/charts/linkerd2"
fuzz "github.com/AdaLogics/go-fuzz-headers"
)
// FuzzInject fuzzes Pod injection.
func FuzzInject(data []byte) int {
f := fuzz.NewConsumer(data)
yamlBytes, err := f.GetBytes()
if err != nil {
return 0
}
v := &l5dcharts.Values{}
err = f.GenerateStruct(v)
if err != nil {
return 0
}
conf := NewResourceConfig(v, OriginUnknown, "")
_, _ = conf.ParseMetaAndYAML(yamlBytes)
injectProxy, err := f.GetBool()
if err != nil {
return 0
}
_, _ = conf.GetPodPatch(injectProxy)
_, _ = conf.CreateOpaquePortsPatch()
report := &Report{}
err = f.GenerateStruct(report)
if err == nil {
_, _ = conf.Uninject(report)
}
return 1
}
package inject
import (
"errors"
"fmt"
"strings"
"github.com/linkerd/linkerd2/pkg/healthcheck"
"github.com/linkerd/linkerd2/pkg/k8s"
v1 "k8s.io/api/core/v1"
)
const (
hostNetworkEnabled = "host_network_enabled"
sidecarExists = "sidecar_already_exists"
unsupportedResource = "unsupported_resource"
injectEnableAnnotationAbsent = "injection_enable_annotation_absent"
injectDisableAnnotationPresent = "injection_disable_annotation_present"
annotationAtNamespace = "namespace"
annotationAtWorkload = "workload"
invalidInjectAnnotationWorkload = "invalid_inject_annotation_at_workload"
invalidInjectAnnotationNamespace = "invalid_inject_annotation_at_ns"
disabledAutomountServiceAccountToken = "disabled_automount_service_account_token_account"
udpPortsEnabled = "udp_ports_enabled"
)
var (
// Reasons is a map of inject skip reasons with human readable sentences
Reasons = map[string]string{
hostNetworkEnabled: "hostNetwork is enabled",
sidecarExists: "pod has a sidecar injected already",
unsupportedResource: "this resource kind is unsupported",
injectEnableAnnotationAbsent: fmt.Sprintf("neither the namespace nor the pod have the annotation \"%s:%s\"", k8s.ProxyInjectAnnotation, k8s.ProxyInjectEnabled),
injectDisableAnnotationPresent: fmt.Sprintf("pod has the annotation \"%s:%s\"", k8s.ProxyInjectAnnotation, k8s.ProxyInjectDisabled),
invalidInjectAnnotationWorkload: fmt.Sprintf("invalid value for annotation \"%s\" at workload", k8s.ProxyInjectAnnotation),
invalidInjectAnnotationNamespace: fmt.Sprintf("invalid value for annotation \"%s\" at namespace", k8s.ProxyInjectAnnotation),
disabledAutomountServiceAccountToken: "automountServiceAccountToken set to \"false\", with Values.identity.serviceAccountTokenProjection set to \"false\"",
udpPortsEnabled: "UDP port(s) configured on pod spec",
}
)
// Report contains the Kind and Name for a given workload along with booleans
// describing the result of the injection transformation
type Report struct {
Kind string
Name string
HostNetwork bool
Sidecar bool
UDP bool // true if any port in any container has `protocol: UDP`
UnsupportedResource bool // unsupported to inject
InjectDisabled bool
InjectDisabledReason string
InjectAnnotationAt string
Annotatable bool
Annotated bool
AutomountServiceAccountToken bool
// Uninjected consists of two boolean flags to indicate if a proxy and
// proxy-init containers have been uninjected in this report
Uninjected struct {
// Proxy is true if a proxy container has been uninjected
Proxy bool
// ProxyInit is true if a proxy-init container has been uninjected
ProxyInit bool
}
}
// newReport returns a new Report struct, initialized with the Kind and Name
// from conf
func newReport(conf *ResourceConfig) *Report {
var name string
if conf.IsPod() {
name = conf.pod.meta.Name
if name == "" {
name = conf.pod.meta.GenerateName
}
} else if m := conf.workload.Meta; m != nil {
name = m.Name
}
report := &Report{
Kind: strings.ToLower(conf.workload.metaType.Kind),
Name: name,
AutomountServiceAccountToken: true,
}
if conf.HasPodTemplate() {
report.InjectDisabled, report.InjectDisabledReason, report.InjectAnnotationAt = report.disabledByAnnotation(conf)
report.HostNetwork = conf.pod.spec.HostNetwork
report.Sidecar = healthcheck.HasExistingSidecars(conf.pod.spec)
report.UDP = checkUDPPorts(conf.pod.spec)
if conf.pod.spec.AutomountServiceAccountToken != nil &&
(conf.values != nil && !conf.values.Identity.ServiceAccountTokenProjection) {
report.AutomountServiceAccountToken = *conf.pod.spec.AutomountServiceAccountToken
}
if conf.origin == OriginWebhook {
if vm := conf.serviceAccountVolumeMount(); vm == nil {
// set to false only if it is not using the new linkerd-token volume projection
if conf.values != nil && !conf.values.Identity.ServiceAccountTokenProjection {
report.AutomountServiceAccountToken = false
}
}
}
} else {
report.UnsupportedResource = true
}
if conf.HasPodTemplate() || conf.IsService() || conf.IsNamespace() {
report.Annotatable = true
}
return report
}
// ResName returns a string "Kind/Name" for the workload referred in the report r
func (r *Report) ResName() string {
return fmt.Sprintf("%s/%s", r.Kind, r.Name)
}
// Injectable returns false if the report flags indicate that the workload is on a host network
// or there is already a sidecar or the resource is not supported or inject is explicitly disabled.
// If false, the second returned value describes the reason.
func (r *Report) Injectable() (bool, []string) {
var reasons []string
if r.HostNetwork {
reasons = append(reasons, hostNetworkEnabled)
}
if r.Sidecar {
reasons = append(reasons, sidecarExists)
}
if r.UnsupportedResource {
reasons = append(reasons, unsupportedResource)
}
if r.InjectDisabled {
reasons = append(reasons, r.InjectDisabledReason)
}
if !r.AutomountServiceAccountToken {
reasons = append(reasons, disabledAutomountServiceAccountToken)
}
if len(reasons) > 0 {
return false, reasons
}
return true, nil
}
// IsAnnotatable returns true if the resource for a report can be annotated.
func (r *Report) IsAnnotatable() bool {
return r.Annotatable
}
func checkUDPPorts(t *v1.PodSpec) bool {
// Check for ports with `protocol: UDP`, which will not be routed by Linkerd
for _, container := range t.Containers {
for _, port := range container.Ports {
if port.Protocol == v1.ProtocolUDP {
return true
}
}
}
return false
}
// disabledByAnnotation checks the workload and namespace for the annotation
// that disables injection. It returns if it is disabled, why it is disabled,
// and the location where the annotation was present.
func (r *Report) disabledByAnnotation(conf *ResourceConfig) (bool, string, string) {
// truth table of the effects of the inject annotation:
//
// origin | namespace | pod | inject? | return
// ------- | --------- | -------- | -------- | ------
// webhook | enabled | enabled | yes | false
// webhook | enabled | "" | yes | false
// webhook | enabled | disabled | no | true
// webhook | disabled | enabled | yes | false
// webhook | "" | enabled | yes | false
// webhook | disabled | disabled | no | true
// webhook | "" | disabled | no | true
// webhook | disabled | "" | no | true
// webhook | "" | "" | no | true
// cli | n/a | enabled | yes | false
// cli | n/a | "" | yes | false
// cli | n/a | disabled | no | true
podAnnotation := conf.pod.meta.Annotations[k8s.ProxyInjectAnnotation]
nsAnnotation := conf.nsAnnotations[k8s.ProxyInjectAnnotation]
if conf.origin == OriginCLI {
return podAnnotation == k8s.ProxyInjectDisabled, "", ""
}
if !isInjectAnnotationValid(nsAnnotation) {
return true, invalidInjectAnnotationNamespace, ""
}
if !isInjectAnnotationValid(podAnnotation) {
return true, invalidInjectAnnotationWorkload, ""
}
if nsAnnotation == k8s.ProxyInjectEnabled || nsAnnotation == k8s.ProxyInjectIngress {
if podAnnotation == k8s.ProxyInjectDisabled {
return true, injectDisableAnnotationPresent, annotationAtWorkload
}
return false, "", annotationAtNamespace
}
if podAnnotation != k8s.ProxyInjectEnabled && podAnnotation != k8s.ProxyInjectIngress {
return true, injectEnableAnnotationAbsent, ""
}
return false, "", annotationAtWorkload
}
func isInjectAnnotationValid(annotation string) bool {
if annotation != "" && !(annotation == k8s.ProxyInjectEnabled || annotation == k8s.ProxyInjectDisabled || annotation == k8s.ProxyInjectIngress) {
return false
}
return true
}
// ThrowInjectError errors out `inject` when the report contains errors
// related to automountServiceAccountToken, hostNetwork, existing sidecar,
// or udp ports
// See - https://github.com/linkerd/linkerd2/issues/4214
func (r *Report) ThrowInjectError() []error {
errs := []error{}
if !r.AutomountServiceAccountToken {
errs = append(errs, errors.New(Reasons[disabledAutomountServiceAccountToken]))
}
if r.HostNetwork {
errs = append(errs, errors.New(Reasons[hostNetworkEnabled]))
}
if r.Sidecar {
errs = append(errs, errors.New(Reasons[sidecarExists]))
}
if r.UDP {
errs = append(errs, errors.New(Reasons[udpPortsEnabled]))
}
return errs
}
package inject
import (
"strings"
"github.com/linkerd/linkerd2/pkg/k8s"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// Uninject removes from the workload in conf the init and proxy containers,
// the TLS volumes and the extra annotations/labels that were added
func (conf *ResourceConfig) Uninject(report *Report) ([]byte, error) {
if conf.IsNamespace() || conf.IsService() {
uninjectObjectMeta(conf.workload.Meta, report)
return conf.YamlMarshalObj()
}
if conf.pod.spec == nil {
return nil, nil
}
conf.uninjectPodSpec(report)
if conf.workload.Meta != nil {
uninjectObjectMeta(conf.workload.Meta, report)
}
uninjectObjectMeta(conf.pod.meta, report)
return conf.YamlMarshalObj()
}
// Given a PodSpec, update the PodSpec in place with the sidecar
// and init-container uninjected
func (conf *ResourceConfig) uninjectPodSpec(report *Report) {
t := conf.pod.spec
initContainers := []v1.Container{}
for _, container := range t.InitContainers {
switch container.Name {
case k8s.InitContainerName:
report.Uninjected.ProxyInit = true
case k8s.ProxyContainerName:
report.Uninjected.Proxy = true
default:
initContainers = append(initContainers, container)
}
}
t.InitContainers = initContainers
containers := []v1.Container{}
for _, container := range t.Containers {
if container.Name != k8s.ProxyContainerName {
containers = append(containers, container)
} else {
report.Uninjected.Proxy = true
}
}
t.Containers = containers
volumes := []v1.Volume{}
for _, volume := range t.Volumes {
if volume.Name != k8s.IdentityEndEntityVolumeName && volume.Name != k8s.InitXtablesLockVolumeMountName && volume.Name != k8s.LinkerdTokenVolumeMountName {
volumes = append(volumes, volume)
}
}
t.Volumes = volumes
}
func uninjectObjectMeta(t *metav1.ObjectMeta, report *Report) {
// We only uninject control plane components in the context
// of doing an inject --manual. This is done as a way to update
// something about the injection configuration - for example
// adding a debug sidecar to the identity service.
// With that in mind it is not really necessary to strip off
// the linkerd.io/* metadata from the pod during uninjection.
// This is why we skip that part for control plane components.
// Furthermore the latter will never have linkerd.io/inject as
// they are always manually injected.
if _, ok := t.Labels[k8s.ControllerComponentLabel]; !ok {
newAnnotations := make(map[string]string)
for key, val := range t.Annotations {
if !strings.HasPrefix(key, k8s.Prefix) ||
(key == k8s.ProxyInjectAnnotation && val == k8s.ProxyInjectDisabled) {
newAnnotations[key] = val
} else {
report.Uninjected.Proxy = true
}
}
t.Annotations = newAnnotations
labels := make(map[string]string)
for key, val := range t.Labels {
if !strings.HasPrefix(key, k8s.Prefix) {
labels[key] = val
}
}
t.Labels = labels
}
}
package issuercerts
import (
"context"
"crypto/ecdsa"
"crypto/rsa"
"crypto/x509"
"fmt"
"os"
"path/filepath"
"time"
"k8s.io/client-go/kubernetes"
"github.com/linkerd/linkerd2/pkg/k8s"
"github.com/linkerd/linkerd2/pkg/tls"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const keyMissingError = "key %s containing the %s needs to exist in secret %s if --identity-external-issuer=%v"
const expirationWarningThresholdInDays = 60
// IssuerCertData holds the trust anchors cert data used by the CA
type IssuerCertData struct {
TrustAnchors string
IssuerCrt string
IssuerKey string
Expiry *time.Time
}
// FetchIssuerData fetches the issuer data from the linkerd-identity-issuer secrets (used for linkerd.io/tls schemed secrets)
func FetchIssuerData(ctx context.Context, api kubernetes.Interface, trustAnchors, controlPlaneNamespace string) (*IssuerCertData, error) {
secret, err := api.CoreV1().Secrets(controlPlaneNamespace).Get(ctx, k8s.IdentityIssuerSecretName, metav1.GetOptions{})
if err != nil {
return nil, err
}
crt, ok := secret.Data[k8s.IdentityIssuerCrtName]
if !ok {
return nil, fmt.Errorf(keyMissingError, k8s.IdentityIssuerCrtName, "issuer certificate", k8s.IdentityIssuerSecretName, false)
}
key, ok := secret.Data[k8s.IdentityIssuerKeyName]
if !ok {
return nil, fmt.Errorf(keyMissingError, k8s.IdentityIssuerKeyName, "issuer key", k8s.IdentityIssuerSecretName, true)
}
cert, err := tls.DecodePEMCrt(string(crt))
if err != nil {
return nil, fmt.Errorf("could not parse issuer certificate: %w", err)
}
return &IssuerCertData{trustAnchors, string(crt), string(key), &cert.Certificate.NotAfter}, nil
}
// FetchExternalIssuerData fetches the issuer data from the linkerd-identity-issuer secrets (used for kubernetes.io/tls schemed secrets)
func FetchExternalIssuerData(ctx context.Context, api kubernetes.Interface, controlPlaneNamespace string) (*IssuerCertData, error) {
secret, err := api.CoreV1().Secrets(controlPlaneNamespace).Get(ctx, k8s.IdentityIssuerSecretName, metav1.GetOptions{})
if err != nil {
return nil, err
}
anchors, ok := secret.Data[k8s.IdentityIssuerTrustAnchorsNameExternal]
if !ok {
return nil, fmt.Errorf(keyMissingError, k8s.IdentityIssuerTrustAnchorsNameExternal, "trust anchors", k8s.IdentityIssuerSecretName, true)
}
crt, ok := secret.Data[corev1.TLSCertKey]
if !ok {
return nil, fmt.Errorf(keyMissingError, corev1.TLSCertKey, "issuer certificate", k8s.IdentityIssuerSecretName, true)
}
key, ok := secret.Data[corev1.TLSPrivateKeyKey]
if !ok {
return nil, fmt.Errorf(keyMissingError, corev1.TLSPrivateKeyKey, "issuer key", k8s.IdentityIssuerSecretName, true)
}
cert, err := tls.DecodePEMCrt(string(crt))
if err != nil {
return nil, fmt.Errorf("could not parse issuer certificate: %w", err)
}
return &IssuerCertData{string(anchors), string(crt), string(key), &cert.Certificate.NotAfter}, nil
}
// LoadIssuerCrtAndKeyFromFiles loads the issuer certificate and key from files
func LoadIssuerCrtAndKeyFromFiles(keyPEMFile, crtPEMFile string) (string, string, error) {
key, err := os.ReadFile(filepath.Clean(keyPEMFile))
if err != nil {
return "", "", err
}
crt, err := os.ReadFile(filepath.Clean(crtPEMFile))
if err != nil {
return "", "", err
}
return string(key), string(crt), nil
}
// LoadIssuerDataFromFiles loads the issuer data from file stored on disk
func LoadIssuerDataFromFiles(keyPEMFile, crtPEMFile, trustPEMFile string) (*IssuerCertData, error) {
key, crt, err := LoadIssuerCrtAndKeyFromFiles(keyPEMFile, crtPEMFile)
if err != nil {
return nil, err
}
anchors, err := os.ReadFile(filepath.Clean(trustPEMFile))
if err != nil {
return nil, err
}
return &IssuerCertData{string(anchors), crt, key, nil}, nil
}
// CheckCertValidityPeriod ensures the certificate is valid time - wise
func CheckCertValidityPeriod(cert *x509.Certificate) error {
if cert.NotBefore.After(time.Now()) {
return fmt.Errorf("not valid before: %s", cert.NotBefore.Format(time.RFC3339))
}
if cert.NotAfter.Before(time.Now()) {
return fmt.Errorf("not valid anymore. Expired on %s", cert.NotAfter.Format(time.RFC3339))
}
return nil
}
// CheckExpiringSoon returns an error if a certificate is expiring soon
func CheckExpiringSoon(cert *x509.Certificate) error {
if time.Now().AddDate(0, 0, expirationWarningThresholdInDays).After(cert.NotAfter) {
return fmt.Errorf("will expire on %s", cert.NotAfter.Format(time.RFC3339))
}
return nil
}
// CheckIssuerCertAlgoRequirements ensures the certificate respects with the constraints
// we have posed on the public key and signature algorithms. Issuer certificates can only
// be signed by an ECDSA certificate.
func CheckIssuerCertAlgoRequirements(cert *x509.Certificate) error {
if cert.PublicKeyAlgorithm == x509.ECDSA {
err := checkECDSACertRequirements(cert)
if err != nil {
return err
}
} else {
return fmt.Errorf("issuer certificate must use ECDSA for public key algorithm, instead %s was used", cert.PublicKeyAlgorithm)
}
return nil
}
// CheckTrustAnchorAlgoRequirements ensures the certificate respects with the constraints
// we have posed on the public key and signature algorithms. Trust anchors can be signed by
// an ECDSA or RSA certificate.
func CheckTrustAnchorAlgoRequirements(cert *x509.Certificate) error {
if cert.PublicKeyAlgorithm == x509.ECDSA {
err := checkECDSACertRequirements(cert)
if err != nil {
return err
}
} else if cert.PublicKeyAlgorithm == x509.RSA {
err := checkRSACertRequirements(cert)
if err != nil {
return err
}
} else {
return fmt.Errorf("trust anchor must use ECDSA or RSA for public key algorithm, instead %s was used", cert.PublicKeyAlgorithm)
}
return nil
}
func checkECDSACertRequirements(cert *x509.Certificate) error {
k, ok := cert.PublicKey.(*ecdsa.PublicKey)
if !ok {
return fmt.Errorf("expected ecdsa.PublicKey but got something %v", cert.PublicKey)
}
if k.Params().BitSize != 256 {
return fmt.Errorf("must use P-256 curve for public key, instead P-%d was used", k.Params().BitSize)
}
if cert.SignatureAlgorithm != x509.ECDSAWithSHA256 &&
cert.SignatureAlgorithm != x509.SHA256WithRSA {
return fmt.Errorf("must be signed by an ECDSA P-256 key, instead %s was used", cert.SignatureAlgorithm)
}
return nil
}
func checkRSACertRequirements(cert *x509.Certificate) error {
k, ok := cert.PublicKey.(*rsa.PublicKey)
if !ok {
return fmt.Errorf("expected rsa.PublicKey but got something %v", cert.PublicKey)
}
if k.N.BitLen() != 2048 && k.N.BitLen() != 4096 {
return fmt.Errorf("RSA must use at least 2084 bit public key, instead %d bit public key was used", k.N.BitLen())
}
if cert.SignatureAlgorithm != x509.SHA256WithRSA {
return fmt.Errorf("must be signed by an RSA 2048/4096 bit key, instead %s was used", cert.SignatureAlgorithm)
}
return nil
}
// VerifyAndBuildCreds builds and validates the creds out of the data in IssuerCertData
func (ic *IssuerCertData) VerifyAndBuildCreds() (*tls.Cred, error) {
creds, err := tls.ValidateAndCreateCreds(ic.IssuerCrt, ic.IssuerKey)
if err != nil {
return nil, fmt.Errorf("failed to read CA: %w", err)
}
// we check the time validity of the issuer cert
if err := CheckCertValidityPeriod(creds.Certificate); err != nil {
return nil, err
}
// we check the algo requirements of the issuer cert
if err := CheckIssuerCertAlgoRequirements(creds.Certificate); err != nil {
return nil, err
}
if !creds.Certificate.IsCA {
return nil, fmt.Errorf("issuer cert is not a CA")
}
anchors, err := tls.DecodePEMCertPool(ic.TrustAnchors)
if err != nil {
return nil, err
}
if err := creds.Verify(anchors, "", time.Time{}); err != nil {
return nil, err
}
return creds, nil
}
package k8s
import (
"context"
"errors"
"fmt"
"net/http"
"strings"
"time"
crdclient "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned"
"github.com/linkerd/linkerd2/pkg/prometheus"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/version"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
apiregistration "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
// Load all the auth plugins for the cloud providers.
_ "k8s.io/client-go/plugin/pkg/client/auth"
)
var minAPIVersion = [3]int{1, 22, 0}
// KubernetesAPI provides a client for accessing a Kubernetes cluster.
// TODO: support ServiceProfile ClientSet. A prerequisite is moving the
// ServiceProfile client code from `./controller` to `./pkg` (#2751). This will
// also allow making `NewFakeClientSets` private, as KubernetesAPI will support
// all relevant k8s resources.
type KubernetesAPI struct {
*rest.Config
kubernetes.Interface
Apiextensions apiextensionsclient.Interface // for CRDs
Apiregistration apiregistration.Interface // for access to APIService
DynamicClient dynamic.Interface
L5dCrdClient crdclient.Interface
}
// NewAPI validates a Kubernetes config and returns a client for accessing the
// configured cluster.
func NewAPI(configPath, kubeContext string, impersonate string, impersonateGroup []string, timeout time.Duration) (*KubernetesAPI, error) {
config, err := GetConfig(configPath, kubeContext)
if err != nil {
return nil, fmt.Errorf("error configuring Kubernetes API client: %w", err)
}
return NewAPIForConfig(config, impersonate, impersonateGroup, timeout, 0, 0)
}
// NewAPIForConfig uses a Kubernetes config to construct a client for accessing
// the configured cluster
func NewAPIForConfig(
config *rest.Config,
impersonate string,
impersonateGroup []string,
timeout time.Duration,
qps float32,
burst int,
) (*KubernetesAPI, error) {
// k8s' client-go doesn't support injecting context
// https://github.com/kubernetes/kubernetes/issues/46503
// but we can set the timeout manually
config.Timeout = timeout
if qps > 0 && burst > 0 {
config.QPS = qps
config.Burst = burst
prometheus.SetClientQPS("k8s", config.QPS)
prometheus.SetClientBurst("k8s", config.Burst)
} else {
prometheus.SetClientQPS("k8s", rest.DefaultQPS)
prometheus.SetClientBurst("k8s", rest.DefaultBurst)
}
wt := config.WrapTransport
config.WrapTransport = prometheus.ClientWithTelemetry("k8s", wt)
if impersonate != "" {
config.Impersonate = rest.ImpersonationConfig{
UserName: impersonate,
Groups: impersonateGroup,
}
}
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, fmt.Errorf("error configuring Kubernetes API clientset: %w", err)
}
apiextensions, err := apiextensionsclient.NewForConfig(config)
if err != nil {
return nil, fmt.Errorf("error configuring Kubernetes API Extensions clientset: %w", err)
}
aggregatorClient, err := apiregistration.NewForConfig(config)
if err != nil {
return nil, fmt.Errorf("error configuring Kubernetes API server aggregator: %w", err)
}
dynamicClient, err := dynamic.NewForConfig(config)
if err != nil {
return nil, fmt.Errorf("error configuring Kubernetes Dynamic Client: %w", err)
}
l5dCrdClient, err := crdclient.NewForConfig(config)
if err != nil {
return nil, fmt.Errorf("error configuring Linkerd CRD clientset: %w", err)
}
return &KubernetesAPI{
Config: config,
Interface: clientset,
Apiextensions: apiextensions,
Apiregistration: aggregatorClient,
DynamicClient: dynamicClient,
L5dCrdClient: l5dCrdClient,
}, nil
}
// NewClient returns an http.Client configured with a Transport to connect to
// the Kubernetes cluster.
func (kubeAPI *KubernetesAPI) NewClient() (*http.Client, error) {
secureTransport, err := rest.TransportFor(kubeAPI.Config)
if err != nil {
return nil, fmt.Errorf("error instantiating Kubernetes API client: %w", err)
}
return &http.Client{
Transport: secureTransport,
}, nil
}
// GetVersionInfo returns version.Info for the Kubernetes cluster.
func (kubeAPI *KubernetesAPI) GetVersionInfo() (*version.Info, error) {
return kubeAPI.Discovery().ServerVersion()
}
// CheckVersion validates whether the configured Kubernetes cluster's version is
// running a minimum Kubernetes API version.
func (kubeAPI *KubernetesAPI) CheckVersion(versionInfo *version.Info) error {
apiVersion, err := getK8sVersion(versionInfo.String())
if err != nil {
return err
}
if !isCompatibleVersion(minAPIVersion, apiVersion) {
return fmt.Errorf("Kubernetes is on version [%d.%d.%d], but version [%d.%d.%d] or more recent is required",
apiVersion[0], apiVersion[1], apiVersion[2],
minAPIVersion[0], minAPIVersion[1], minAPIVersion[2])
}
return nil
}
// NamespaceExists validates whether a given namespace exists.
func (kubeAPI *KubernetesAPI) NamespaceExists(ctx context.Context, namespace string) (bool, error) {
ns, err := kubeAPI.GetNamespace(ctx, namespace)
if kerrors.IsNotFound(err) {
return false, nil
}
if err != nil {
return false, err
}
return ns != nil, nil
}
// GetNamespace returns the namespace with a given name, if one exists.
func (kubeAPI *KubernetesAPI) GetNamespace(ctx context.Context, namespace string) (*corev1.Namespace, error) {
return kubeAPI.CoreV1().Namespaces().Get(ctx, namespace, metav1.GetOptions{})
}
// GetNodes returns all the nodes in a cluster.
func (kubeAPI *KubernetesAPI) GetNodes(ctx context.Context) ([]corev1.Node, error) {
nodes, err := kubeAPI.CoreV1().Nodes().List(ctx, metav1.ListOptions{})
if err != nil {
return nil, err
}
return nodes.Items, nil
}
// GetPodsByNamespace returns all pods in a given namespace
func (kubeAPI *KubernetesAPI) GetPodsByNamespace(ctx context.Context, namespace string) ([]corev1.Pod, error) {
podList, err := kubeAPI.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{})
if err != nil {
return nil, err
}
return podList.Items, nil
}
// GetReplicaSets returns all replicasets in a given namespace
func (kubeAPI *KubernetesAPI) GetReplicaSets(ctx context.Context, namespace string) ([]appsv1.ReplicaSet, error) {
replicaSetList, err := kubeAPI.AppsV1().ReplicaSets(namespace).List(ctx, metav1.ListOptions{})
if err != nil {
return nil, err
}
return replicaSetList.Items, nil
}
// GetAllNamespacesWithExtensionLabel gets all namespaces with the linkerd.io/extension label key
func (kubeAPI *KubernetesAPI) GetAllNamespacesWithExtensionLabel(ctx context.Context) ([]corev1.Namespace, error) {
namespaces, err := kubeAPI.CoreV1().Namespaces().List(ctx, metav1.ListOptions{LabelSelector: LinkerdExtensionLabel})
if err != nil {
return nil, err
}
return namespaces.Items, nil
}
// GetNamespaceWithExtensionLabel gets the namespace with the LinkerdExtensionLabel label value of `value`
func (kubeAPI *KubernetesAPI) GetNamespaceWithExtensionLabel(ctx context.Context, value string) (*corev1.Namespace, error) {
namespaces, err := kubeAPI.GetAllNamespacesWithExtensionLabel(ctx)
if err != nil {
return nil, err
}
for _, ns := range namespaces {
if ns.Labels[LinkerdExtensionLabel] == value {
return &ns, err
}
}
errNotFound := kerrors.NewNotFound(corev1.Resource("namespace"), value)
errNotFound.ErrStatus.Message = fmt.Sprintf("namespace with label \"%s: %s\" not found", LinkerdExtensionLabel, value)
return nil, errNotFound
}
// GetPodStatus receives a pod and returns the pod status, based on `kubectl` logic.
// This logic is imported and adapted from the github.com/kubernetes/kubernetes project:
// https://github.com/kubernetes/kubernetes/blob/v1.31.0-alpha.0/pkg/printers/internalversion/printers.go#L860
func GetPodStatus(pod corev1.Pod) string {
reason := string(pod.Status.Phase)
if pod.Status.Reason != "" {
reason = pod.Status.Reason
}
initContainers := make(map[string]*corev1.Container)
for i := range pod.Spec.InitContainers {
initContainers[pod.Spec.InitContainers[i].Name] = &pod.Spec.InitContainers[i]
}
initializing := false
for i := range pod.Status.InitContainerStatuses {
container := pod.Status.InitContainerStatuses[i]
switch {
case container.State.Terminated != nil && container.State.Terminated.ExitCode == 0 && container.State.Terminated.Signal == 0:
continue
case isRestartableInitContainer(initContainers[container.Name]) &&
container.Started != nil && *container.Started && container.Ready:
continue
case container.State.Terminated != nil:
// initialization is failed
if container.State.Terminated.Reason == "" {
if container.State.Terminated.Signal != 0 {
reason = fmt.Sprintf("Init:Signal:%d", container.State.Terminated.Signal)
} else {
reason = fmt.Sprintf("Init:ExitCode:%d", container.State.Terminated.ExitCode)
}
} else {
reason = "Init:" + container.State.Terminated.Reason
}
initializing = true
case container.State.Waiting != nil && len(container.State.Waiting.Reason) > 0 && container.State.Waiting.Reason != "PodInitializing":
reason = "Init:" + container.State.Waiting.Reason
initializing = true
default:
reason = fmt.Sprintf("Init:%d/%d", i, len(pod.Spec.InitContainers))
initializing = true
}
break
}
if !initializing {
hasRunning := false
for i := len(pod.Status.ContainerStatuses) - 1; i >= 0; i-- {
container := pod.Status.ContainerStatuses[i]
if container.State.Waiting != nil && container.State.Waiting.Reason != "" {
reason = container.State.Waiting.Reason
} else if container.State.Terminated != nil && container.State.Terminated.Reason != "" {
reason = container.State.Terminated.Reason
} else if container.State.Terminated != nil && container.State.Terminated.Reason == "" {
if container.State.Terminated.Signal != 0 {
reason = fmt.Sprintf("Signal:%d", container.State.Terminated.Signal)
} else {
reason = fmt.Sprintf("ExitCode:%d", container.State.Terminated.ExitCode)
}
} else if container.Ready && container.State.Running != nil {
hasRunning = true
}
}
// change pod status back to "Running" if there is at least one container still reporting as "Running" status
if reason == "Completed" && hasRunning {
reason = "Running"
}
}
return reason
}
// Borrowed from
// https://github.com/kubernetes/kubernetes/blob/v1.31.0-alpha.0/pkg/printers/internalversion/printers.go#L3209
func isRestartableInitContainer(initContainer *corev1.Container) bool {
if initContainer.RestartPolicy == nil {
return false
}
return *initContainer.RestartPolicy == corev1.ContainerRestartPolicyAlways
}
// GetProxyReady returns true if the pod contains a proxy that is ready
func GetProxyReady(pod corev1.Pod) bool {
statuses := append(pod.Status.InitContainerStatuses, pod.Status.ContainerStatuses...)
for _, container := range statuses {
if container.Name == ProxyContainerName {
return container.Ready
}
}
return false
}
// GetProxyVersion returns the container proxy's version, if any
func GetProxyVersion(pod corev1.Pod) string {
containers := append(pod.Spec.InitContainers, pod.Spec.Containers...)
for _, container := range containers {
if container.Name == ProxyContainerName {
if strings.Contains(container.Image, "@") {
// Proxy container image is specified with digest instead of
// tag. We are unable to determine version.
return ""
}
parts := strings.Split(container.Image, ":")
return parts[len(parts)-1]
}
}
return ""
}
// GetPodsFor takes a resource string, queries the Kubernetes API, and returns a
// list of pods belonging to that resource.
func GetPodsFor(ctx context.Context, clientset kubernetes.Interface, namespace string, resource string) ([]corev1.Pod, error) {
elems := strings.Split(resource, "/")
if len(elems) == 1 {
return nil, errors.New("no resource name provided")
}
if len(elems) != 2 {
return nil, fmt.Errorf("invalid resource string: %s", resource)
}
typ, err := CanonicalResourceNameFromFriendlyName(elems[0])
if err != nil {
return nil, err
}
name := elems[1]
// special case if a single pod was specified
if typ == Pod {
pod, err := clientset.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return nil, err
}
return []corev1.Pod{*pod}, nil
}
var matchLabels map[string]string
var ownerUID types.UID
switch typ {
case CronJob:
jobs, err := clientset.BatchV1().Jobs(namespace).List(ctx, metav1.ListOptions{})
if err != nil {
return nil, err
}
var pods []corev1.Pod
for _, job := range jobs.Items {
if isOwner(job.GetUID(), job.GetOwnerReferences()) {
jobPods, err := GetPodsFor(ctx, clientset, namespace, fmt.Sprintf("%s/%s", Job, job.GetName()))
if err != nil {
return nil, err
}
pods = append(pods, jobPods...)
}
}
return pods, nil
case DaemonSet:
ds, err := clientset.AppsV1().DaemonSets(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return nil, err
}
matchLabels = ds.Spec.Selector.MatchLabels
ownerUID = ds.GetUID()
case Deployment:
deployment, err := clientset.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return nil, err
}
matchLabels = deployment.Spec.Selector.MatchLabels
ownerUID = deployment.GetUID()
replicaSets, err := clientset.AppsV1().ReplicaSets(namespace).List(
ctx,
metav1.ListOptions{
LabelSelector: labels.Set(matchLabels).AsSelector().String(),
},
)
if err != nil {
return nil, err
}
var pods []corev1.Pod
for _, rs := range replicaSets.Items {
if isOwner(ownerUID, rs.GetOwnerReferences()) {
podsRS, err := GetPodsFor(ctx, clientset, namespace, fmt.Sprintf("%s/%s", ReplicaSet, rs.GetName()))
if err != nil {
return nil, err
}
pods = append(pods, podsRS...)
}
}
return pods, nil
case Job:
job, err := clientset.BatchV1().Jobs(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return nil, err
}
matchLabels = job.Spec.Selector.MatchLabels
ownerUID = job.GetUID()
case ReplicaSet:
rs, err := clientset.AppsV1().ReplicaSets(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return nil, err
}
matchLabels = rs.Spec.Selector.MatchLabels
ownerUID = rs.GetUID()
case ReplicationController:
rc, err := clientset.CoreV1().ReplicationControllers(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return nil, err
}
matchLabels = rc.Spec.Selector
ownerUID = rc.GetUID()
case StatefulSet:
ss, err := clientset.AppsV1().StatefulSets(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return nil, err
}
matchLabels = ss.Spec.Selector.MatchLabels
ownerUID = ss.GetUID()
default:
return nil, fmt.Errorf("unsupported resource type: %s", name)
}
podList, err := clientset.
CoreV1().
Pods(namespace).
List(
ctx,
metav1.ListOptions{
LabelSelector: labels.Set(matchLabels).AsSelector().String(),
},
)
if err != nil {
return nil, err
}
if ownerUID == "" {
return podList.Items, nil
}
pods := []corev1.Pod{}
for _, pod := range podList.Items {
if isOwner(ownerUID, pod.GetOwnerReferences()) {
pods = append(pods, pod)
}
}
return pods, nil
}
func isOwner(u types.UID, ownerRefs []metav1.OwnerReference) bool {
for _, or := range ownerRefs {
if u == or.UID {
return true
}
}
return false
}
package k8s
import (
"context"
"errors"
"fmt"
authV1 "k8s.io/api/authorization/v1"
discovery "k8s.io/api/discovery/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes"
)
// ResourceAuthz checks whether a given Kubernetes client is authorized to
// perform a given action.
func ResourceAuthz(
ctx context.Context,
k8sClient kubernetes.Interface,
namespace, verb, group, version, resource, name string,
) error {
ssar := &authV1.SelfSubjectAccessReview{
Spec: authV1.SelfSubjectAccessReviewSpec{
ResourceAttributes: &authV1.ResourceAttributes{
Namespace: namespace,
Verb: verb,
Group: group,
Version: version,
Resource: resource,
Name: name,
},
},
}
result, err := k8sClient.
AuthorizationV1().
SelfSubjectAccessReviews().
Create(ctx, ssar, metav1.CreateOptions{})
if err != nil {
return err
}
return evaluateAccessReviewStatus(group, resource, result.Status)
}
// ResourceAuthzForUser checks whether a given user is authorized to perform a
// given action.
func ResourceAuthzForUser(
ctx context.Context,
client kubernetes.Interface,
namespace, verb, group, version, resource, subresource, name, user string, userGroups []string) error {
sar := &authV1.SubjectAccessReview{
Spec: authV1.SubjectAccessReviewSpec{
User: user,
Groups: userGroups,
ResourceAttributes: &authV1.ResourceAttributes{
Namespace: namespace,
Verb: verb,
Group: group,
Version: version,
Resource: resource,
Subresource: subresource,
Name: name,
},
},
}
result, err := client.
AuthorizationV1().
SubjectAccessReviews().
Create(ctx, sar, metav1.CreateOptions{})
if err != nil {
return err
}
return evaluateAccessReviewStatus(group, resource, result.Status)
}
func evaluateAccessReviewStatus(group, resource string, status authV1.SubjectAccessReviewStatus) error {
if status.Allowed {
return nil
}
gk := schema.GroupKind{
Group: group,
Kind: resource,
}
if len(status.Reason) > 0 {
return fmt.Errorf("not authorized to access %s: %s", gk, status.Reason)
}
return fmt.Errorf("not authorized to access %s", gk)
}
// ServiceProfilesAccess checks whether the ServiceProfile CRD is installed
// on the cluster and the client is authorized to access ServiceProfiles.
func ServiceProfilesAccess(ctx context.Context, k8sClient kubernetes.Interface) error {
res, err := k8sClient.Discovery().ServerResourcesForGroupVersion(ServiceProfileAPIVersion)
if err != nil {
return err
}
if res.GroupVersion == ServiceProfileAPIVersion {
for _, apiRes := range res.APIResources {
if apiRes.Kind == ServiceProfileKind {
return ResourceAuthz(ctx, k8sClient, "", "list", "linkerd.io", "", "serviceprofiles", "")
}
}
}
return errors.New("ServiceProfile CRD not found")
}
// ServersAccess checks whether the Server CRD is installed on the cluster
// and the client is authorized to access Servers.
func ServersAccess(ctx context.Context, k8sClient kubernetes.Interface) error {
groupVersion := fmt.Sprintf("%s/%s", PolicyAPIGroup, PolicyServerCRDVersion)
res, err := k8sClient.Discovery().ServerResourcesForGroupVersion(groupVersion)
if err != nil {
return err
}
if res.GroupVersion == groupVersion {
for _, apiRes := range res.APIResources {
if apiRes.Kind == ServerKind {
return ResourceAuthz(ctx, k8sClient, "", "list", PolicyAPIGroup, "", "servers", "")
}
}
}
return fmt.Errorf("server CRD (%s) not found", groupVersion)
}
// ExtWorkloadAccess checks whether the ExternalWorkload CRD is installed on the
// cluster and the client is authorized to access ExternalWorkloads
func ExtWorkloadAccess(ctx context.Context, k8sClient kubernetes.Interface) error {
groupVersion := fmt.Sprintf("%s/%s", WorkloadAPIGroup, WorkloadAPIVersion)
res, err := k8sClient.Discovery().ServerResourcesForGroupVersion(groupVersion)
if err != nil {
return err
}
if res.GroupVersion == groupVersion {
for _, apiRes := range res.APIResources {
if apiRes.Kind == ExtWorkloadKind {
return ResourceAuthz(ctx, k8sClient, "", "list", WorkloadAPIGroup, "", "externalworkloads", "")
}
}
}
return errors.New("ExternalWorkload CRD not found")
}
// EndpointSliceAccess verifies whether the K8s cluster has
// access to EndpointSlice resources.
func EndpointSliceAccess(ctx context.Context, k8sClient kubernetes.Interface) error {
gv := discovery.SchemeGroupVersion.String()
res, err := k8sClient.Discovery().ServerResourcesForGroupVersion(gv)
if err != nil {
return err
}
if res.GroupVersion == gv {
for _, apiRes := range res.APIResources {
if apiRes.Kind == "EndpointSlice" {
return checkEndpointSlicesExist(ctx, k8sClient)
}
}
}
return errors.New("EndpointSlice resource not found")
}
func checkEndpointSlicesExist(ctx context.Context, k8sClient kubernetes.Interface) error {
sliceList, err := k8sClient.DiscoveryV1().EndpointSlices("").List(ctx, metav1.ListOptions{})
if err != nil {
return err
}
if len(sliceList.Items) > 0 {
return nil
}
return errors.New("no EndpointSlice resources exist in the cluster")
}
// ClusterAccess verifies whether k8sClient is authorized to access all pods in
// all namespaces in the cluster.
func ClusterAccess(ctx context.Context, k8sClient kubernetes.Interface) error {
return ResourceAuthz(ctx, k8sClient, "", "list", "", "", "pods", "")
}
package k8s
import (
"context"
"fmt"
"strings"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// CommandCompletion generates CLI suggestions from resources in a given cluster
// It uses a list of arguments and a substring from the CLI to filter suggestions.
type CommandCompletion struct {
k8sAPI *KubernetesAPI
namespace string
}
// NewCommandCompletion creates a command completion module
func NewCommandCompletion(
k8sAPI *KubernetesAPI,
namespace string,
) *CommandCompletion {
return &CommandCompletion{
k8sAPI: k8sAPI,
namespace: namespace,
}
}
// Complete accepts a list of arguments and a substring to generate CLI suggestions.
// `args` represent a list of arguments a user has already entered in the CLI. These
// arguments are used for determining what resource type we'd like to receive
// suggestions for as well as a list of resources names that have already provided.
// `toComplete` represents the string prefix of a resource name that we'd like to
// use to search for suggestions
//
// If `args` is empty, send back a list of all resource types support by the CLI.
// If `args` has at least one or more items, assume that the first item in `args`
// is the resource type we are trying to get suggestions for e.g. Deployment, StatefulSets.
//
// Complete is generic enough so that it can find suggestions for any type of resource
// in a Kubernetes cluster. It does this by first querying what GroupVersion a resource
// belongs to and then does a dynamic `List` query to get resources under that GroupVersion
func (c *CommandCompletion) Complete(args []string, toComplete string) ([]string, error) {
ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
defer cancelFn()
suggestions := []string{}
if len(args) == 0 && toComplete == "" {
return CompletionResourceTypes, nil
}
if len(args) == 0 && toComplete != "" {
for _, t := range CompletionResourceTypes {
if strings.HasPrefix(t, toComplete) {
suggestions = append(suggestions, t)
}
}
return suggestions, nil
}
// Similar to kubectl, we don't provide resource completion
// when the resource provided is in format <kind>/<resourceName>
if strings.Contains(args[0], "/") {
return []string{}, nil
}
resType, err := CanonicalResourceNameFromFriendlyName(args[0])
if err != nil {
return nil, fmt.Errorf("%s is not a valid resource name", args)
}
// if we are looking for namespace suggestions clear namespace selector
if resType == "namespace" {
c.namespace = ""
}
gvr, err := c.getGroupVersionKindForResource(resType)
if err != nil {
return nil, err
}
uList, err := c.k8sAPI.DynamicClient.
Resource(*gvr).
Namespace(c.namespace).
List(ctx, metav1.ListOptions{})
if err != nil {
return nil, err
}
otherResources := args[1:]
for _, u := range uList.Items {
name := u.GetName()
// Filter out the list of resource items returned from k8s API to
// only include items that have the prefix `toComplete` and items
// that aren't in the list of resources already provided in the
// list of arguments.
//
// This is useful so that we avoid duplicate suggestions if they
// are already in the list of args
if strings.HasPrefix(name, toComplete) &&
!containsResource(name, otherResources) {
suggestions = append(suggestions, name)
}
}
return suggestions, nil
}
func (c *CommandCompletion) getGroupVersionKindForResource(resourceName string) (*schema.GroupVersionResource, error) {
_, apiResourceList, err := c.k8sAPI.Discovery().ServerGroupsAndResources()
if err != nil {
return nil, err
}
// find the plural name to ensure the resource we are searching for is not a subresource
// i.e. deployment/scale
pluralResourceName, err := PluralResourceNameFromFriendlyName(resourceName)
if err != nil {
return nil, fmt.Errorf("%s not a valid resource name", resourceName)
}
gvr, err := findGroupVersionResource(resourceName, pluralResourceName, apiResourceList)
if err != nil {
return nil, fmt.Errorf("could not find GroupVersionResource for %s", resourceName)
}
return gvr, nil
}
func findGroupVersionResource(singularName string, pluralName string, apiResourceList []*metav1.APIResourceList) (*schema.GroupVersionResource, error) {
err := fmt.Errorf("could not find the requested resource")
for _, res := range apiResourceList {
for _, r := range res.APIResources {
// Make sure we get a resource type where its Kind matches the
// singularName passed into this function and its Name (which is always
// the pluralName of an api resource) matches the pluralName passed
// into this function. Skip further processing of this APIResource
// if this is not the case.
if strings.ToLower(r.Kind) != singularName || r.Name != pluralName {
continue
}
gv := strings.Split(res.GroupVersion, "/")
if len(gv) == 1 && gv[0] == "v1" {
return &schema.GroupVersionResource{
Version: gv[0],
Resource: r.Name,
}, nil
}
if len(gv) != 2 {
return nil, err
}
return &schema.GroupVersionResource{
Group: gv[0],
Version: gv[1],
Resource: r.Name,
}, nil
}
}
return nil, err
}
func containsResource(resource string, otherResources []string) bool {
for _, r := range otherResources {
if r == resource {
return true
}
}
return false
}
package k8s
import (
"bufio"
"errors"
"fmt"
"io"
"strings"
spclient "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned"
spfake "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/fake"
spscheme "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme"
corev1 "k8s.io/api/core/v1"
discovery "k8s.io/api/discovery/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
apiextensionsfake "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/rand"
yamlDecoder "k8s.io/apimachinery/pkg/util/yaml"
discoveryfake "k8s.io/client-go/discovery/fake"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/client-go/testing"
apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
apiregistrationclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
apiregistrationfake "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake"
"sigs.k8s.io/yaml"
)
func init() {
apiextensionsv1beta1.AddToScheme(scheme.Scheme)
apiextensionsv1.AddToScheme(scheme.Scheme)
apiregistrationv1.AddToScheme(scheme.Scheme)
spscheme.AddToScheme(scheme.Scheme)
}
// NewFakeAPI provides a mock KubernetesAPI backed by hard-coded resources
func NewFakeAPI(configs ...string) (*KubernetesAPI, error) {
client, apiextClient, apiregClient, _, err := NewFakeClientSets(configs...)
if err != nil {
return nil, err
}
return &KubernetesAPI{
Config: &rest.Config{},
Interface: client,
Apiextensions: apiextClient,
Apiregistration: apiregClient,
}, nil
}
// NewFakeAPIFromManifests reads from a slice of readers, each representing a
// manifest or collection of manifests, and returns a mock KubernetesAPI.
func NewFakeAPIFromManifests(readers []io.Reader) (*KubernetesAPI, error) {
client, apiextClient, apiregClient, _, err := newFakeClientSetsFromManifests(readers)
if err != nil {
return nil, err
}
return &KubernetesAPI{
Interface: client,
Apiextensions: apiextClient,
Apiregistration: apiregClient,
}, nil
}
// NewFakeClientSets provides mock Kubernetes ClientSets.
// TODO: make this private once KubernetesAPI (and NewFakeAPI) supports spClient
func NewFakeClientSets(configs ...string) (
*fake.Clientset,
apiextensionsclient.Interface,
apiregistrationclient.Interface,
spclient.Interface,
error,
) {
objs := []runtime.Object{}
apiextObjs := []runtime.Object{}
apiRegObjs := []runtime.Object{}
discoveryObjs := []runtime.Object{}
spObjs := []runtime.Object{}
for _, config := range configs {
obj, err := ToRuntimeObject(config)
if err != nil {
return nil, nil, nil, nil, err
}
switch strings.ToLower(obj.GetObjectKind().GroupVersionKind().Kind) {
case "customresourcedefinition":
apiextObjs = append(apiextObjs, obj)
case "apiservice":
apiRegObjs = append(apiRegObjs, obj)
case "apiresourcelist":
discoveryObjs = append(discoveryObjs, obj)
case ServiceProfile:
spObjs = append(spObjs, obj)
case Server:
spObjs = append(spObjs, obj)
case ExtWorkload:
spObjs = append(spObjs, obj)
default:
objs = append(objs, obj)
}
}
endpointslice, err := ToRuntimeObject(`apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: kubernetes
namespace: default`)
if err != nil {
return nil, nil, nil, nil, err
}
objs = append(objs, endpointslice)
cs := fake.NewSimpleClientset(objs...)
fakeDiscoveryClient := cs.Discovery().(*discoveryfake.FakeDiscovery)
for _, obj := range discoveryObjs {
apiResList := obj.(*metav1.APIResourceList)
fakeDiscoveryClient.Resources = append(fakeDiscoveryClient.Resources, apiResList)
}
fakeDiscoveryClient.Resources = append(fakeDiscoveryClient.Resources, &metav1.APIResourceList{
TypeMeta: metav1.TypeMeta{
Kind: "APIResourceList",
APIVersion: "v1",
},
GroupVersion: discovery.SchemeGroupVersion.String(),
APIResources: []metav1.APIResource{
{
Name: "endpointslices",
Kind: "EndpointSlice",
SingularName: "endpointslice",
},
},
})
// Add helpers to work with endpoint slice objects.
cs.PrependReactor("create", "endpointslices", testing.ReactionFunc(func(action testing.Action) (bool, runtime.Object, error) {
es := action.(testing.CreateAction).GetObject().(*discovery.EndpointSlice)
// The API Server cannot generate a name when we use mocks, intercept
// the object and change its name
if es.GenerateName != "" {
es.Name = fmt.Sprintf("%s-%s", es.GenerateName, rand.String(8))
es.GenerateName = ""
}
es.Generation = 1
return false, es, nil
}))
cs.PrependReactor("update", "endpointslices", testing.ReactionFunc(func(action testing.Action) (bool, runtime.Object, error) {
// An update won't increase the generation since the API Server is
// mocked, so do a typecast and increment it here.
es := action.(testing.CreateAction).GetObject().(*discovery.EndpointSlice)
es.Generation++
return false, es, nil
}))
return cs,
apiextensionsfake.NewSimpleClientset(apiextObjs...),
apiregistrationfake.NewSimpleClientset(apiRegObjs...),
spfake.NewSimpleClientset(spObjs...),
nil
}
// newFakeClientSetsFromManifests reads from a slice of readers, each
// representing a manifest or collection of manifests, and returns a mock
// Kubernetes ClientSet.
//
//nolint:unparam
func newFakeClientSetsFromManifests(readers []io.Reader) (
kubernetes.Interface,
apiextensionsclient.Interface,
apiregistrationclient.Interface,
spclient.Interface,
error,
) {
configs := []string{}
for _, reader := range readers {
r := yamlDecoder.NewYAMLReader(bufio.NewReaderSize(reader, 4096))
// Iterate over all YAML objects in the input
for {
// Read a single YAML object
bytes, err := r.Read()
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return nil, nil, nil, nil, err
}
// check for kind
var typeMeta metav1.TypeMeta
if err := yaml.Unmarshal(bytes, &typeMeta); err != nil {
return nil, nil, nil, nil, err
}
switch typeMeta.Kind {
case "":
// Kind missing from YAML, skipping
case "List":
var sourceList corev1.List
if err := yaml.Unmarshal(bytes, &sourceList); err != nil {
return nil, nil, nil, nil, err
}
for _, item := range sourceList.Items {
configs = append(configs, string(item.Raw))
}
default:
configs = append(configs, string(bytes))
}
}
}
return NewFakeClientSets(configs...)
}
// ToRuntimeObject deserializes Kubernetes YAML into a Runtime Object
func ToRuntimeObject(config string) (runtime.Object, error) {
decode := scheme.Codecs.UniversalDeserializer().Decode
obj, _, err := decode([]byte(config), nil, nil)
return obj, err
}
// ObjectKinds wraps client-go's scheme.Scheme.ObjectKinds()
// It returns all possible group,version,kind of the go object, true if the
// object is considered unversioned, or an error if it's not a pointer or is
// unregistered.
func ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) {
apiextensionsv1beta1.AddToScheme(scheme.Scheme)
apiextensionsv1.AddToScheme(scheme.Scheme)
apiregistrationv1.AddToScheme(scheme.Scheme)
spscheme.AddToScheme(scheme.Scheme)
return scheme.Scheme.ObjectKinds(obj)
}
package k8s
import (
"fmt"
"strings"
corev1 "k8s.io/api/core/v1"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)
// These constants are string representations of Kubernetes resource types.
const (
All = "all"
Authority = "authority"
ConfigMap = "configmap"
CronJob = "cronjob"
DaemonSet = "daemonset"
Deployment = "deployment"
Endpoints = "endpoints"
EndpointSlices = "endpointslices"
ExtWorkload = "externalworkload"
Job = "job"
MeshTLSAuthentication = "meshtlsauthentication"
MutatingWebhookConfig = "mutatingwebhookconfig"
Namespace = "namespace"
NetworkAuthentication = "networkauthentication"
Pod = "pod"
ReplicationController = "replicationcontroller"
ReplicaSet = "replicaset"
Secret = "secret"
Service = "service"
ServiceProfile = "serviceprofile"
StatefulSet = "statefulset"
Node = "node"
Server = "server"
ServerAuthorization = "serverauthorization"
AuthorizationPolicy = "authorizationpolicy"
HTTPRoute = "httproute"
PolicyAPIGroup = "policy.linkerd.io"
PolicyServerCRDVersion = "v1beta2"
ServiceProfileAPIVersion = "linkerd.io/v1alpha2"
ServiceProfileKind = "ServiceProfile"
LinkAPIGroup = "multicluster.linkerd.io"
LinkAPIVersion = "v1alpha1"
LinkAPIGroupVersion = "multicluster.linkerd.io/v1alpha1"
LinkKind = "Link"
K8sCoreAPIGroup = "core"
NamespaceKind = "Namespace"
ServerKind = "Server"
HTTPRouteKind = "HTTPRoute"
ExtWorkloadKind = "ExternalWorkload"
PodKind = "Pod"
WorkloadAPIGroup = "workload.linkerd.io"
WorkloadAPIVersion = "v1alpha1"
// special case k8s job label, to not conflict with Prometheus' job label
l5dJob = "k8s_job"
)
type resourceName struct {
short string
full string
plural string
}
// AllResources is a sorted list of all resources defined as constants above.
var AllResources = []string{
Authority,
AuthorizationPolicy,
CronJob,
DaemonSet,
Deployment,
HTTPRoute,
Job,
Namespace,
Pod,
ReplicaSet,
ReplicationController,
Server,
ServerAuthorization,
Service,
ServiceProfile,
StatefulSet,
}
// StatAllResourceTypes represents the resources to query in StatSummary when Resource.Type is "all"
var StatAllResourceTypes = []string{
DaemonSet,
StatefulSet,
Job,
Deployment,
ReplicationController,
Pod,
Service,
Authority,
CronJob,
ReplicaSet,
}
// CompletionResourceTypes represents resources the CLI's uses for autocompleting resource type names
var CompletionResourceTypes = []string{
Namespace,
DaemonSet,
StatefulSet,
Job,
Deployment,
ReplicationController,
Pod,
Service,
Authority,
CronJob,
ReplicaSet,
}
var resourceNames = []resourceName{
{"au", "authority", "authorities"},
{"cj", "cronjob", "cronjobs"},
{"ds", "daemonset", "daemonsets"},
{"deploy", "deployment", "deployments"},
{"job", "job", "jobs"},
{"meshtlsauthn", "meshtlsauthentication", "meshtlsauthentications"},
{"ns", "namespace", "namespaces"},
{"netauthn", "networkauthentication", "networkauthentications"},
{"networkauthn", "networkauthentication", "networkauthentications"},
{"po", "pod", "pods"},
{"rc", "replicationcontroller", "replicationcontrollers"},
{"rs", "replicaset", "replicasets"},
{"svc", "service", "services"},
{"sp", "serviceprofile", "serviceprofiles"},
{"saz", "serverauthorization", "serverauthorizations"},
{"serverauthz", "serverauthorization", "serverauthorizations"},
{"srvauthz", "serverauthorization", "serverauthorizations"},
{"srv", "server", "servers"},
{"ap", "authorizationpolicy", "authorizationpolicies"},
{"httproute", "httproute", "httproutes"},
{"authzpolicy", "authorizationpolicy", "authorizationpolicies"},
{"sts", "statefulset", "statefulsets"},
{"ln", "link", "links"},
{"all", "all", "all"},
}
// GetConfig returns kubernetes config based on the current environment.
// If fpath is provided, loads configuration from that file. Otherwise,
// GetConfig uses default strategy to load configuration from $KUBECONFIG,
// .kube/config, or just returns in-cluster config.
func GetConfig(fpath, kubeContext string) (*rest.Config, error) {
rules := clientcmd.NewDefaultClientConfigLoadingRules()
if fpath != "" {
rules.ExplicitPath = fpath
}
overrides := &clientcmd.ConfigOverrides{CurrentContext: kubeContext}
return clientcmd.
NewNonInteractiveDeferredLoadingClientConfig(rules, overrides).
ClientConfig()
}
// CanonicalResourceNameFromFriendlyName returns a canonical name from common shorthands used in command line tools.
// This works based on https://github.com/kubernetes/kubernetes/blob/63ffb1995b292be0a1e9ebde6216b83fc79dd988/pkg/kubectl/kubectl.go#L39
// This also works for non-k8s resources, e.g. authorities
func CanonicalResourceNameFromFriendlyName(friendlyName string) (string, error) {
for _, name := range resourceNames {
if friendlyName == name.short || friendlyName == name.full || friendlyName == name.plural {
return name.full, nil
}
}
return "", fmt.Errorf("cannot find Kubernetes canonical name from friendly name [%s]", friendlyName)
}
// PluralResourceNameFromFriendlyName returns a pluralized canonical name from common shorthands used in command line tools.
// This works based on https://github.com/kubernetes/kubernetes/blob/63ffb1995b292be0a1e9ebde6216b83fc79dd988/pkg/kubectl/kubectl.go#L39
// This also works for non-k8s resources, e.g. authorities
func PluralResourceNameFromFriendlyName(friendlyName string) (string, error) {
for _, name := range resourceNames {
if friendlyName == name.short || friendlyName == name.full || friendlyName == name.plural {
return name.plural, nil
}
}
return "", fmt.Errorf("cannot find Kubernetes canonical name from friendly name [%s]", friendlyName)
}
// ShortNameFromCanonicalResourceName returns the shortest name for a k8s canonical name.
// Essentially the reverse of CanonicalResourceNameFromFriendlyName
func ShortNameFromCanonicalResourceName(canonicalName string) string {
switch canonicalName {
case Authority:
return "au"
case CronJob:
return "cj"
case DaemonSet:
return "ds"
case Deployment:
return "deploy"
case Job:
return "job"
case Namespace:
return "ns"
case Pod:
return "po"
case ReplicationController:
return "rc"
case ReplicaSet:
return "rs"
case Service:
return "svc"
case ServiceProfile:
return "sp"
case StatefulSet:
return "sts"
default:
return ""
}
}
// KindToL5DLabel converts a Kubernetes `kind` to a Linkerd label.
// For example:
//
// `pod` -> `pod`
// `job` -> `k8s_job`
func KindToL5DLabel(k8sKind string) string {
if k8sKind == Job {
return l5dJob
}
return k8sKind
}
// PodIdentity returns the mesh TLS identity name of this pod, as constructed
// from the pod's service account name and other metadata.
func PodIdentity(pod *corev1.Pod) (string, error) {
if pod.Status.Phase != corev1.PodRunning {
return "", fmt.Errorf("pod not running: %s", pod.GetName())
}
podsa := pod.Spec.ServiceAccountName
podns := pod.ObjectMeta.Namespace
containers := append(pod.Spec.InitContainers, pod.Spec.Containers...)
for _, c := range containers {
if c.Name == ProxyContainerName {
for _, env := range c.Env {
if env.Name == "LINKERD2_PROXY_IDENTITY_LOCAL_NAME" {
return strings.ReplaceAll(env.Value, "$(_pod_sa).$(_pod_ns)", fmt.Sprintf("%s.%s", podsa, podns)), nil
}
}
}
}
return "", nil
}
/*
Kubernetes labels and annotations used in Linkerd's control plane and data plane
Kubernetes configs.
*/
package k8s
import (
"fmt"
ewv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1"
"github.com/linkerd/linkerd2/pkg/version"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
)
const (
/*
* Labels
*/
// Prefix is the prefix common to all labels and annotations injected by Linkerd
Prefix = "linkerd.io"
// LinkerdExtensionLabel is a label that helps identifying the namespace
// that contain a Linkerd Extension
LinkerdExtensionLabel = Prefix + "/extension"
// ControllerComponentLabel identifies this object as a component of Linkerd's
// control plane (e.g. web, controller).
ControllerComponentLabel = Prefix + "/control-plane-component"
// ExtensionAPIServerAuthenticationConfigMapName is the name of the ConfigMap where
// authentication data for extension API servers is placed.
ExtensionAPIServerAuthenticationConfigMapName = "extension-apiserver-authentication"
// ExtensionAPIServerAuthenticationRequestHeaderClientCAFileKey is the key that
// contains the value of the "--requestheader-client-ca-file" flag.
ExtensionAPIServerAuthenticationRequestHeaderClientCAFileKey = "requestheader-client-ca-file"
// RequireIDHeader signals to the proxy that a certain identity should be expected
// of the remote peer
RequireIDHeader = "l5d-require-id"
// ControllerNSLabel is injected into mesh-enabled apps, identifying the
// namespace of the Linkerd control plane.
ControllerNSLabel = Prefix + "/control-plane-ns"
// ProxyDeploymentLabel is injected into mesh-enabled apps, identifying the
// deployment that this proxy belongs to.
ProxyDeploymentLabel = Prefix + "/proxy-deployment"
// ProxyReplicationControllerLabel is injected into mesh-enabled apps,
// identifying the ReplicationController that this proxy belongs to.
ProxyReplicationControllerLabel = Prefix + "/proxy-replicationcontroller"
// ProxyReplicaSetLabel is injected into mesh-enabled apps, identifying the
// ReplicaSet that this proxy belongs to.
ProxyReplicaSetLabel = Prefix + "/proxy-replicaset"
// ProxyJobLabel is injected into mesh-enabled apps, identifying the Job that
// this proxy belongs to.
ProxyJobLabel = Prefix + "/proxy-job"
// ProxyDaemonSetLabel is injected into mesh-enabled apps, identifying the
// DaemonSet that this proxy belongs to.
ProxyDaemonSetLabel = Prefix + "/proxy-daemonset"
// ProxyStatefulSetLabel is injected into mesh-enabled apps, identifying the
// StatefulSet that this proxy belongs to.
ProxyStatefulSetLabel = Prefix + "/proxy-statefulset"
// ProxyCronJobLabel is injected into mesh-enabled apps, identifying the
// CronJob that this proxy belongs to.
ProxyCronJobLabel = Prefix + "/proxy-cronjob"
// WorkloadNamespaceLabel is injected into mesh-enabled apps, identifying the
// Namespace that this proxy belongs to.
WorkloadNamespaceLabel = Prefix + "/workload-ns"
// Enabled is used by annotations whose valid values include "enabled".
Enabled = "enabled"
// Disabled is used by annotations whose valid values include "disabled".
Disabled = "disabled"
/*
* Annotations
*/
// CreatedByAnnotation indicates the source of the injected data plane
// (e.g. linkerd/cli v2.0.0).
CreatedByAnnotation = Prefix + "/created-by"
// ProxyVersionAnnotation indicates the version of the injected data plane
// (e.g. v0.1.3).
ProxyVersionAnnotation = Prefix + "/proxy-version"
// ProxyInjectAnnotation controls whether or not a pod should be injected
// when set on a pod spec. When set on a namespace spec, it applies to all
// pods in the namespace. Supported values are Enabled or Disabled
ProxyInjectAnnotation = Prefix + "/inject"
// ProxyInjectEnabled is assigned to the ProxyInjectAnnotation annotation to
// enable injection for a pod or namespace.
ProxyInjectEnabled = Enabled
// ProxyInjectIngress is assigned to the ProxyInjectAnnotation annotation to
// enable injection in ingress mode for a pod.
ProxyInjectIngress = "ingress"
// ProxyInjectDisabled is assigned to the ProxyInjectAnnotation annotation to
// disable injection for a pod or namespace.
ProxyInjectDisabled = Disabled
// ProxyTrustRootSHA indicates the cert bundle configured on the injected
// workload.
ProxyTrustRootSHA = Prefix + "/trust-root-sha256"
/*
* Proxy config annotations
*/
// ProxyConfigAnnotationsPrefix is the prefix of all config-related annotations
ProxyConfigAnnotationsPrefix = "config.linkerd.io"
// ProxyConfigAnnotationsPrefixAlpha is the prefix of newly released config-related annotations
ProxyConfigAnnotationsPrefixAlpha = "config.alpha.linkerd.io"
// ProxyImageAnnotation can be used to override the proxyImage config.
ProxyImageAnnotation = ProxyConfigAnnotationsPrefix + "/proxy-image"
// ProxyImagePullPolicyAnnotation can be used to override the
// proxyImagePullPolicy and proxyInitImagePullPolicy configs.
ProxyImagePullPolicyAnnotation = ProxyConfigAnnotationsPrefix + "/image-pull-policy"
// ProxyInitImageAnnotation can be used to override the proxyInitImage
// config.
ProxyInitImageAnnotation = ProxyConfigAnnotationsPrefix + "/init-image"
// ProxyInitImageVersionAnnotation can be used to override the proxy-init image version
ProxyInitImageVersionAnnotation = ProxyConfigAnnotationsPrefix + "/init-image-version"
// DebugImageAnnotation can be used to override the debugImage config.
DebugImageAnnotation = ProxyConfigAnnotationsPrefix + "/debug-image"
// DebugImageVersionAnnotation can be used to override the debugImageVersion config.
DebugImageVersionAnnotation = ProxyConfigAnnotationsPrefix + "/debug-image-version"
// DebugImagePullPolicyAnnotation can be used to override the debugImagePullPolicy config.
DebugImagePullPolicyAnnotation = ProxyConfigAnnotationsPrefix + "/debug-image-pull-policy"
// ProxyControlPortAnnotation can be used to override the controlPort config.
ProxyControlPortAnnotation = ProxyConfigAnnotationsPrefix + "/control-port"
// ProxyIgnoreInboundPortsAnnotation can be used to override the
// ignoreInboundPorts config.
ProxyIgnoreInboundPortsAnnotation = ProxyConfigAnnotationsPrefix + "/skip-inbound-ports"
// ProxyOpaquePortsAnnotation can be used to override the opaquePorts
// config.
ProxyOpaquePortsAnnotation = ProxyConfigAnnotationsPrefix + "/opaque-ports"
// ProxyIgnoreOutboundPortsAnnotation can be used to override the
// ignoreOutboundPorts config.
ProxyIgnoreOutboundPortsAnnotation = ProxyConfigAnnotationsPrefix + "/skip-outbound-ports"
// ProxySkipSubnetsAnnotation can be used to override the skipSubnets config
ProxySkipSubnetsAnnotation = ProxyConfigAnnotationsPrefix + "/skip-subnets"
// ProxyInboundPortAnnotation can be used to override the inboundPort config.
ProxyInboundPortAnnotation = ProxyConfigAnnotationsPrefix + "/inbound-port"
// ProxyAdminPortAnnotation can be used to override the adminPort config.
ProxyAdminPortAnnotation = ProxyConfigAnnotationsPrefix + "/admin-port"
// ProxyOutboundPortAnnotation can be used to override the outboundPort
// config.
ProxyOutboundPortAnnotation = ProxyConfigAnnotationsPrefix + "/outbound-port"
// ProxyPodInboundPortsAnnotation can be used to set a comma-separated
// list of (non-proxy) container ports exposed by the pod spec. Useful
// when other mutating webhooks inject sidecar containers after the
// proxy injector has run.
ProxyPodInboundPortsAnnotation = ProxyConfigAnnotationsPrefix + "/pod-inbound-ports"
// ProxyCPURequestAnnotation can be used to override the requestCPU config.
ProxyCPURequestAnnotation = ProxyConfigAnnotationsPrefix + "/proxy-cpu-request"
// ProxyMemoryRequestAnnotation can be used to override the
// requestMemoryConfig.
ProxyMemoryRequestAnnotation = ProxyConfigAnnotationsPrefix + "/proxy-memory-request"
// ProxyEphemeralStorageRequestAnnotation can be used to override the requestEphemeralStorage config.
ProxyEphemeralStorageRequestAnnotation = ProxyConfigAnnotationsPrefix + "/proxy-ephemeral-storage-request"
// ProxyCPULimitAnnotation can be used to override the limitCPU config.
ProxyCPULimitAnnotation = ProxyConfigAnnotationsPrefix + "/proxy-cpu-limit"
// ProxyMemoryLimitAnnotation can be used to override the limitMemory config.
ProxyMemoryLimitAnnotation = ProxyConfigAnnotationsPrefix + "/proxy-memory-limit"
// ProxyEphemeralStorageLimitAnnotation can be used to override the limitEphemeralStorage config.
ProxyEphemeralStorageLimitAnnotation = ProxyConfigAnnotationsPrefix + "/proxy-ephemeral-storage-limit"
// ProxyUIDAnnotation can be used to override the UID config.
ProxyUIDAnnotation = ProxyConfigAnnotationsPrefix + "/proxy-uid"
// ProxyGIDAnnotation can be used to override the GID config.
ProxyGIDAnnotation = ProxyConfigAnnotationsPrefix + "/proxy-gid"
// ProxyAdminShutdownAnnotation can be used to override the
// LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED config.
ProxyAdminShutdownAnnotation = ProxyConfigAnnotationsPrefix + "/proxy-admin-shutdown"
// ProxyLogLevelAnnotation can be used to override the log level config.
ProxyLogLevelAnnotation = ProxyConfigAnnotationsPrefix + "/proxy-log-level"
// ProxyLogHTTPHeaders can be used to override if the proxy is permitted to log HTTP headers.
ProxyLogHTTPHeaders = ProxyConfigAnnotationsPrefix + "/proxy-log-http-headers"
// ProxyLogFormatAnnotation can be used to override the log format config.
ProxyLogFormatAnnotation = ProxyConfigAnnotationsPrefix + "/proxy-log-format"
// ProxyEnableExternalProfilesAnnotation can be used to override the
// disableExternalProfilesAnnotation config.
ProxyEnableExternalProfilesAnnotation = ProxyConfigAnnotationsPrefix + "/enable-external-profiles"
// ProxyVersionOverrideAnnotation can be used to override the proxy version config.
ProxyVersionOverrideAnnotation = ProxyConfigAnnotationsPrefix + "/proxy-version"
// ProxyRequireIdentityOnInboundPortsAnnotation can be used to configure the proxy
// to always require identity on inbound ports
ProxyRequireIdentityOnInboundPortsAnnotation = ProxyConfigAnnotationsPrefix + "/proxy-require-identity-inbound-ports"
// ProxyOutboundConnectTimeout can be used to configure the outbound TCP connection
// timeout in the proxy
ProxyOutboundConnectTimeout = ProxyConfigAnnotationsPrefix + "/proxy-outbound-connect-timeout"
// ProxyInboundConnectTimeout can be used to configure the inbound TCP connection
// timeout in the proxy
ProxyInboundConnectTimeout = ProxyConfigAnnotationsPrefix + "/proxy-inbound-connect-timeout"
// ProxyOutboundDiscoveryCacheTimeout can be used to configure the timeout
// that will evict unused outbound discovery results
ProxyOutboundDiscoveryCacheUnusedTimeout = ProxyConfigAnnotationsPrefix + "/proxy-outbound-discovery-cache-unused-timeout"
// ProxyInboundDiscoveryCacheUnusedTimeout can be used to configure the timeout
// that will evict unused inbound discovery results
ProxyInboundDiscoveryCacheUnusedTimeout = ProxyConfigAnnotationsPrefix + "/proxy-inbound-discovery-cache-unused-timeout"
// ProxyDisableOutboundProtocolDetectTimeout can be used to disable protocol
// detection timeouts for outbound connections by setting them to a very
// high value.
ProxyDisableOutboundProtocolDetectTimeout = ProxyConfigAnnotationsPrefix + "/proxy-disable-outbound-protocol-detect-timeout"
// ProxyDisableInboundProtocolDetectTimeout can be used to disable protocol
// detection timeouts for inbound connections by setting them to a very
// high value.
ProxyDisableInboundProtocolDetectTimeout = ProxyConfigAnnotationsPrefix + "/proxy-disable-inbound-protocol-detect-timeout"
// ProxyEnableGatewayAnnotation can be used to configure the proxy
// to operate as a gateway, routing requests that target the inbound router.
ProxyEnableGatewayAnnotation = ProxyConfigAnnotationsPrefix + "/enable-gateway"
// ProxyEnableDebugAnnotation is set to true if the debug container is
// injected.
ProxyEnableDebugAnnotation = ProxyConfigAnnotationsPrefix + "/enable-debug-sidecar"
// CloseWaitTimeoutAnnotation configures nf_conntrack_tcp_timeout_close_wait.
CloseWaitTimeoutAnnotation = ProxyConfigAnnotationsPrefix + "/close-wait-timeout"
// ProxyWaitBeforeExitSecondsAnnotation makes the proxy container to wait for the given period before exiting
// after the Pod entered the Terminating state. Must be smaller than terminationGracePeriodSeconds
// configured for the Pod
ProxyWaitBeforeExitSecondsAnnotation = ProxyConfigAnnotationsPrefixAlpha + "/proxy-wait-before-exit-seconds"
// ProxyEnableNativeSidecarAnnotation enables the new native initContainer sidecar
ProxyEnableNativeSidecarAnnotation = ProxyConfigAnnotationsPrefixAlpha + "/proxy-enable-native-sidecar"
// ProxyAwait can be used to force the application to wait for the proxy
// to be ready.
ProxyAwait = ProxyConfigAnnotationsPrefix + "/proxy-await"
// ProxyDefaultInboundPolicyAnnotation is used to configure the default
// inbound policy of the proxy
ProxyDefaultInboundPolicyAnnotation = ProxyConfigAnnotationsPrefix + "/default-inbound-policy"
// ProxyAccessLogAnnotation configures whether HTTP access logging is
// enabled, and what access log format is used.
ProxyAccessLogAnnotation = ProxyConfigAnnotationsPrefix + "/access-log"
// AllUnauthenticated allows all unathenticated connections.
AllUnauthenticated = "all-unauthenticated"
// AllAuthenticated allows all authenticated connections.
AllAuthenticated = "all-authenticated"
// ClusterUnauthenticated allows all unauthenticated connections from
// within the cluster.
ClusterUnauthenticated = "cluster-unauthenticated"
// ClusterAuthenticated allows all authenticated connections from within
// the cluster.
ClusterAuthenticated = "cluster-authenticated"
// Deny denies all connections.
Deny = "deny"
// ProxyShutdownGracePeriodAnnotation configures the grace period for
// graceful shutdowns in the proxy.
ProxyShutdownGracePeriodAnnotation = ProxyConfigAnnotationsPrefix + "/shutdown-grace-period"
/*
* Component Names
*/
// ConfigConfigMapName is the name of the ConfigMap containing the linkerd controller configuration.
ConfigConfigMapName = "linkerd-config"
// DebugContainerName is the name of the default linkerd debug container
DebugContainerName = "linkerd-debug"
// DebugSidecarImage is the image name of the default linkerd debug container
DebugSidecarImage = "cr.l5d.io/linkerd/debug"
// InitContainerName is the name assigned to the injected init container.
InitContainerName = "linkerd-init"
// InitXtablesLockVolumeMountName is the name of the volumeMount used by proxy-init
// to handle iptables-legacy
InitXtablesLockVolumeMountName = "linkerd-proxy-init-xtables-lock"
// LinkerdTokenVolumeMountName is the name of the volumeMount used for
// the serviceAccount token
LinkerdTokenVolumeMountName = "linkerd-identity-token"
// ProxyContainerName is the name assigned to the injected proxy container.
ProxyContainerName = "linkerd-proxy"
// IdentityEndEntityVolumeName is the name assigned the temporary end-entity
// volume mounted into each proxy to store identity credentials.
IdentityEndEntityVolumeName = "linkerd-identity-end-entity"
// IdentityIssuerSecretName is the name of the Secret that stores issuer credentials.
IdentityIssuerSecretName = "linkerd-identity-issuer"
// IdentityIssuerSchemeLinkerd is the issuer secret scheme used by linkerd
IdentityIssuerSchemeLinkerd = "linkerd.io/tls"
// IdentityIssuerKeyName is the issuer's private key file.
IdentityIssuerKeyName = "key.pem"
// IdentityIssuerCrtName is the issuer's certificate file.
IdentityIssuerCrtName = "crt.pem"
// IdentityIssuerTrustAnchorsNameExternal is the issuer's certificate file (when using cert-manager).
IdentityIssuerTrustAnchorsNameExternal = "ca.crt"
// ProxyPortName is the name of the Linkerd Proxy's proxy port.
ProxyPortName = "linkerd-proxy"
// ProxyAdminPortName is the name of the Linkerd Proxy's metrics port.
ProxyAdminPortName = "linkerd-admin"
// ProxyInjectorWebhookServiceName is the name of the mutating webhook service
ProxyInjectorWebhookServiceName = "linkerd-proxy-injector"
// ProxyInjectorWebhookConfigName is the name of the mutating webhook configuration
ProxyInjectorWebhookConfigName = ProxyInjectorWebhookServiceName + "-webhook-config"
// SPValidatorWebhookServiceName is the name of the validating webhook service
SPValidatorWebhookServiceName = "linkerd-sp-validator"
// SPValidatorWebhookConfigName is the name of the validating webhook configuration
SPValidatorWebhookConfigName = SPValidatorWebhookServiceName + "-webhook-config"
// PolicyValidatorWebhookConfigName is the name of the validating webhook configuration
PolicyValidatorWebhookConfigName = "linkerd-policy-validator-webhook-config"
/*
* Mount paths
*/
// MountPathBase is the base directory of the mount path.
MountPathBase = "/var/run/linkerd"
// MountPathTrustRootsBase is the base directory of the trust roots.
MountPathTrustRootsBase = MountPathBase + "/identity/trust-roots"
// MountPathTrustRootsPEM is the path at which the trust bundle is mounted.
MountPathTrustRootsPEM = MountPathTrustRootsBase + "/ca-bundle.crt"
// MountPathServiceAccount is the default path where Kubernetes stores
// the service account token
MountPathServiceAccount = "/var/run/secrets/kubernetes.io/serviceaccount"
// MountPathValuesConfig is the path at which the values config file is mounted.
MountPathValuesConfig = MountPathBase + "/config/values"
// MountPathTLSBase is the path at which the TLS cert and key PEM files are mounted
MountPathTLSBase = MountPathBase + "/tls"
// MountPathTLSKeyPEM is the path at which the TLS key PEM file is mounted.
MountPathTLSKeyPEM = MountPathTLSBase + "/tls.key"
// MountPathTLSCrtPEM is the path at which the TLS cert PEM file is mounted.
MountPathTLSCrtPEM = MountPathTLSBase + "/tls.crt"
/*
* Service mirror constants
*/
// SvcMirrorPrefix is the prefix common to all labels and annotations
// and types used by the service mirror component
SvcMirrorPrefix = "mirror.linkerd.io"
// MulticlusterPrefix is the prefix common to all labels and annotations
// used for multicluster services.
MulticlusterPrefix = "multicluster.linkerd.io"
// MirrorSecretType is the type of secret that is supposed to contain
// the access information for remote clusters.
MirrorSecretType = SvcMirrorPrefix + "/remote-kubeconfig"
// DefaultExportedServiceSelector is the default label selector for exported
// services.
DefaultExportedServiceSelector = SvcMirrorPrefix + "/exported"
// MirroredResourceLabel indicates that this resource is the result
// of a mirroring operation (can be a namespace or a service)
MirroredResourceLabel = SvcMirrorPrefix + "/mirrored-service"
// MirroredGatewayLabel indicates that this is a mirrored gateway
MirroredGatewayLabel = SvcMirrorPrefix + "/mirrored-gateway"
// MirroredHeadlessSvcNameLabel indicates the root headless service for
// mirrored headless hosts.
MirroredHeadlessSvcNameLabel = SvcMirrorPrefix + "/headless-mirror-svc-name"
// RemoteClusterNameLabel put on a local mirrored service, it
// allows us to associate a mirrored service with a remote cluster
RemoteClusterNameLabel = SvcMirrorPrefix + "/cluster-name"
// RemoteDiscoveryLabel indicates that this service is a remote discovery
// service and the value of this label is the name of the remote cluster.
RemoteDiscoveryLabel = MulticlusterPrefix + "/remote-discovery"
// RemoteServiceLabel is the name of the service in the remote cluster.
RemoteServiceLabel = MulticlusterPrefix + "/remote-service"
// RemoteResourceVersionAnnotation is the last observed remote resource
// version of a mirrored resource. Useful when doing updates
RemoteResourceVersionAnnotation = SvcMirrorPrefix + "/remote-resource-version"
// RemoteServiceFqName is the fully qualified name of the mirrored service
// on the remote cluster
RemoteServiceFqName = SvcMirrorPrefix + "/remote-svc-fq-name"
// RemoteGatewayIdentity follows the same kind of logic as RemoteGatewayNameLabel
RemoteGatewayIdentity = SvcMirrorPrefix + "/remote-gateway-identity"
// GatewayIdentity can be found on the remote gateway service
GatewayIdentity = SvcMirrorPrefix + "/gateway-identity"
// GatewayProbePeriod the interval at which the health of the gateway should be probed
GatewayProbePeriod = SvcMirrorPrefix + "/probe-period"
// GatewayProbePath the path at which the health of the gateway should be probed
GatewayProbePath = SvcMirrorPrefix + "/probe-path"
// ConfigKeyName is the key in the secret that stores the kubeconfig needed to connect
// to a remote cluster
ConfigKeyName = "kubeconfig"
// GatewayPortName is the name of the incoming port of the gateway
GatewayPortName = "mc-gateway"
// ProbePortName is the name of the probe port of the gateway
ProbePortName = "mc-probe"
)
// CreatedByAnnotationValue returns the value associated with
// CreatedByAnnotation.
func CreatedByAnnotationValue() string {
return fmt.Sprintf("linkerd/cli %s", version.Version)
}
// GetServiceAccountAndNS returns the pod's serviceaccount and namespace.
func GetServiceAccountAndNS(pod *corev1.Pod) (sa string, ns string) {
sa = pod.Spec.ServiceAccountName
if sa == "" {
sa = "default"
}
ns = pod.GetNamespace()
if ns == "" {
ns = "default"
}
return
}
// GetPodLabels returns the set of prometheus owner labels for a given pod
func GetPodLabels(ownerKind, ownerName string, pod *corev1.Pod) map[string]string {
labels := map[string]string{"pod": pod.Name}
l5dLabel := KindToL5DLabel(ownerKind)
labels[l5dLabel] = ownerName
labels["serviceaccount"], _ = GetServiceAccountAndNS(pod)
if controllerNS := pod.Labels[ControllerNSLabel]; controllerNS != "" {
labels["control_plane_ns"] = controllerNS
}
if pth := pod.Labels[appsv1.DefaultDeploymentUniqueLabelKey]; pth != "" {
labels["pod_template_hash"] = pth
}
return labels
}
// GetExternalWorkloadLabels returns the set of prometheus owner labels for a given ExternalWorkload
func GetExternalWorkloadLabels(ownerKind, ownerName string, ew *ewv1beta1.ExternalWorkload) map[string]string {
labels := map[string]string{"external_workload": ew.Name}
if ownerKind != "" && ownerName != "" {
labels[ownerKind] = ownerName
}
return labels
}
// IsMeshed returns whether a given Pod is in a given controller's service mesh.
func IsMeshed(pod *corev1.Pod, controllerNS string) bool {
return pod.Labels[ControllerNSLabel] == controllerNS
}
package k8s
import (
"fmt"
"io"
"net/http"
"os"
corev1 "k8s.io/api/core/v1"
)
// AdminHTTPPortName is the name of the port used by the admin http server.
const AdminHTTPPortName string = "admin-http"
// GetContainerMetrics returns the metrics exposed by a container on the passed in portName
func GetContainerMetrics(
k8sAPI *KubernetesAPI,
pod corev1.Pod,
container corev1.Container,
emitLogs bool,
portName string,
) ([]byte, error) {
portForward, err := NewContainerMetricsForward(k8sAPI, pod, container, emitLogs, portName)
if err != nil {
return nil, err
}
defer portForward.Stop()
if err = portForward.Init(); err != nil {
fmt.Fprintf(os.Stderr, "Error running port-forward: %s", err)
return nil, err
}
metricsURL := portForward.URLFor("/metrics")
return getResponse(metricsURL)
}
// getResponse makes a http Get request to the passed url and returns the response/error
func getResponse(url string) ([]byte, error) {
// url has been constructed by k8s.newPortForward and is not passed in by
// the user.
//nolint:gosec
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
package k8s
import (
"context"
"fmt"
"os"
"strings"
"k8s.io/apimachinery/pkg/labels"
policyv1 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1"
serverv1beta2 "github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta2"
serverauthorizationv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/serverauthorization/v1beta1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)
// Authorization holds the names of the resources involved in an authorization.
type Authorization struct {
Route string
Server string
ServerAuthorization string
AuthorizationPolicy string
}
// AuthorizationPolicyGVR is the GroupVersionResource for the AuthorizationPolicy resource.
var AuthorizationPolicyGVR = policyv1.SchemeGroupVersion.WithResource("authorizationpolicies")
// HTTPRouteGVR is the GroupVersionResource for the HTTPRoute resource.
var HTTPRouteGVR = policyv1.SchemeGroupVersion.WithResource("httproutes")
// SazGVR is the GroupVersionResource for the ServerAuthorization resource.
var SazGVR = serverauthorizationv1beta1.SchemeGroupVersion.WithResource("serverauthorizations")
// ServerGVR is the GroupVersionResource for the Server resource.
var ServerGVR = serverv1beta2.SchemeGroupVersion.WithResource("servers")
// AuthorizationsForResource returns a list of ServerAuthorizations and
// AuthorizationPolicies which apply to any Server or HttpRoute which select
// pods belonging to the given resource.
func AuthorizationsForResource(ctx context.Context, k8sAPI *KubernetesAPI, namespace string, resource string) ([]Authorization, error) {
pods, err := getPodsForResourceOrKind(ctx, k8sAPI, namespace, resource, "")
if err != nil {
return nil, err
}
results := make([]Authorization, 0)
sazs, err := k8sAPI.L5dCrdClient.ServerauthorizationV1beta1().ServerAuthorizations(namespace).List(ctx, metav1.ListOptions{})
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get serverauthorization resources: %s\n", err)
os.Exit(1)
}
for _, saz := range sazs.Items {
var servers []serverv1beta2.Server
if saz.Spec.Server.Name != "" {
server, err := k8sAPI.L5dCrdClient.ServerV1beta2().Servers(saz.GetNamespace()).Get(ctx, saz.Spec.Server.Name, metav1.GetOptions{})
if err != nil {
fmt.Fprintf(os.Stderr, "ServerAuthorization/%s targets Server/%s but we failed to get it: %s\n", saz.Name, saz.Spec.Server.Name, err)
continue
}
servers = []serverv1beta2.Server{*server}
} else if saz.Spec.Server.Selector != nil {
selector, err := metav1.LabelSelectorAsSelector(saz.Spec.Server.Selector)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to parse Server selector for ServerAuthorization/%s: %s\n", saz.Name, err)
continue
}
serverList, err := k8sAPI.L5dCrdClient.ServerV1beta2().Servers(saz.GetNamespace()).List(ctx, metav1.ListOptions{LabelSelector: selector.String()})
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get Servers for ServerAuthorization/%s: %s\n", saz.Name, err)
continue
}
servers = serverList.Items
}
for _, server := range servers {
if serverIncludesPod(server, pods) {
results = append(results, Authorization{
Route: "",
Server: server.GetName(),
ServerAuthorization: saz.GetName(),
AuthorizationPolicy: "",
})
}
}
}
policies, err := k8sAPI.L5dCrdClient.PolicyV1alpha1().AuthorizationPolicies(namespace).List(ctx, metav1.ListOptions{})
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get AuthorizationPolicy resources: %s\n", err)
os.Exit(1)
}
allServersInNamespace := map[string]*serverv1beta2.ServerList{}
for _, p := range policies.Items {
target := p.Spec.TargetRef
if target.Kind == NamespaceKind && target.Group == K8sCoreAPIGroup {
serverList, ok := allServersInNamespace[p.Namespace]
if !ok {
serverList, err = k8sAPI.L5dCrdClient.ServerV1beta2().Servers(p.Namespace).List(ctx, metav1.ListOptions{})
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get Servers for Namespace/%s: %s\n", p.Namespace, err)
continue
}
allServersInNamespace[p.Namespace] = serverList
}
for _, server := range serverList.Items {
if serverIncludesPod(server, pods) {
results = append(results, Authorization{
Route: "",
Server: server.GetName(),
ServerAuthorization: "",
AuthorizationPolicy: p.GetName(),
})
}
}
} else if target.Kind == ServerKind && target.Group == PolicyAPIGroup {
server, err := k8sAPI.L5dCrdClient.ServerV1beta2().Servers(p.Namespace).Get(ctx, string(target.Name), metav1.GetOptions{})
if err != nil {
fmt.Fprintf(os.Stderr, "AuthorizationPolicy/%s targets Server/%s but we failed to get it: %s\n", p.Name, target.Name, err)
continue
}
if serverIncludesPod(*server, pods) {
results = append(results, Authorization{
Route: "",
Server: server.GetName(),
ServerAuthorization: "",
AuthorizationPolicy: p.GetName(),
})
}
} else if target.Kind == HTTPRouteKind && target.Group == PolicyAPIGroup {
route, err := k8sAPI.L5dCrdClient.PolicyV1alpha1().HTTPRoutes(p.Namespace).Get(ctx, string(target.Name), metav1.GetOptions{})
if err != nil {
fmt.Fprintf(os.Stderr, "AuthorizationPolicy/%s targets HTTPRoute/%s but we failed to get it: %s\n", p.Name, target.Name, err)
continue
}
for _, parent := range route.Spec.ParentRefs {
if parent.Kind != nil && *parent.Kind == ServerKind &&
parent.Group != nil && *parent.Group == PolicyAPIGroup {
server, err := k8sAPI.L5dCrdClient.ServerV1beta2().Servers(p.Namespace).Get(ctx, string(parent.Name), metav1.GetOptions{})
if err != nil {
fmt.Fprintf(os.Stderr, "HTTPRoute/%s belongs to Server/%s but we failed to get it: %s\n", target.Name, parent.Name, err)
continue
}
if serverIncludesPod(*server, pods) {
results = append(results, Authorization{
Route: route.GetName(),
Server: server.GetName(),
ServerAuthorization: "",
AuthorizationPolicy: p.GetName(),
})
}
}
}
}
}
return results, nil
}
// ServersForResource returns a list of Server names of Servers which select pods
// belonging to the given resource.
func ServersForResource(ctx context.Context, k8sAPI *KubernetesAPI, namespace string, resource string, labelSelector string) ([]string, error) {
pods, err := getPodsForResourceOrKind(ctx, k8sAPI, namespace, resource, labelSelector)
if err != nil {
return nil, err
}
results := make([]string, 0)
servers, err := k8sAPI.L5dCrdClient.ServerV1beta2().Servers(namespace).List(ctx, metav1.ListOptions{})
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get serverauthorization resources: %s\n", err)
os.Exit(1)
}
for _, server := range servers.Items {
if serverIncludesPod(server, pods) {
results = append(results, server.GetName())
}
}
return results, nil
}
// ServerAuthorizationsForServer returns a list of ServerAuthorization names of
// ServerAuthorizations which select the given Server.
func ServerAuthorizationsForServer(ctx context.Context, k8sAPI *KubernetesAPI, namespace string, server string) ([]string, error) {
results := make([]string, 0)
sazs, err := k8sAPI.L5dCrdClient.ServerauthorizationV1beta1().ServerAuthorizations(namespace).List(ctx, metav1.ListOptions{})
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get serverauthorization resources: %s\n", err)
os.Exit(1)
}
for _, saz := range sazs.Items {
if saz.Spec.Server.Name != "" {
s, err := k8sAPI.DynamicClient.Resource(ServerGVR).Namespace(saz.GetNamespace()).Get(ctx, saz.Spec.Server.Name, metav1.GetOptions{})
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get server %s: %s\n", saz.Spec.Server.Name, err)
os.Exit(1)
}
if s.GetName() == server {
results = append(results, saz.GetName())
}
} else if saz.Spec.Server.Selector != nil {
selector, err := metav1.LabelSelectorAsSelector(saz.Spec.Server.Selector)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get servers: %s\n", err)
os.Exit(1)
}
serverList, err := k8sAPI.L5dCrdClient.ServerV1beta2().Servers(saz.GetNamespace()).List(ctx, metav1.ListOptions{LabelSelector: selector.String()})
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get servers: %s\n", err)
os.Exit(1)
}
for _, s := range serverList.Items {
if s.GetName() == server {
results = append(results, saz.GetName())
break
}
}
}
}
return results, nil
}
// serverIncludesPod returns true the given server selects any of the given pods
// and that pod uses the server's port.
func serverIncludesPod(server serverv1beta2.Server, pods []corev1.Pod) bool {
if server.Spec.PodSelector == nil {
return false
}
selector, err := metav1.LabelSelectorAsSelector(server.Spec.PodSelector)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to parse PodSelector of Server/%s: %s\n", server.Name, err)
return false
}
for _, pod := range pods {
if selector.Matches(labels.Set(pod.Labels)) {
for _, container := range pod.Spec.Containers {
for _, p := range container.Ports {
if server.Spec.Port.IntVal == p.ContainerPort || server.Spec.Port.StrVal == p.Name {
return true
}
}
}
}
}
return false
}
// getPodsForResourceOrKind is similar to getPodsForResource, but also supports
// querying for all resources of a given kind (i.e. when resource name is unspecified).
func getPodsForResourceOrKind(ctx context.Context, k8sAPI kubernetes.Interface, namespace string, resource string, labelSelector string) ([]corev1.Pod, error) {
elems := strings.Split(resource, "/")
if len(elems) > 2 {
return nil, fmt.Errorf("invalid resource: %s", resource)
}
if len(elems) == 2 {
pods, err := GetPodsFor(ctx, k8sAPI, namespace, resource)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get pods: %s", err)
os.Exit(1)
}
return pods, nil
}
pods := []corev1.Pod{}
typ, err := CanonicalResourceNameFromFriendlyName(elems[0])
if err != nil {
return nil, fmt.Errorf("invalid resource: %s", resource)
}
selector := metav1.ListOptions{
LabelSelector: labelSelector,
}
switch typ {
case Pod:
ps, err := k8sAPI.CoreV1().Pods(namespace).List(ctx, selector)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get pods: %s", err)
os.Exit(1)
}
pods = append(pods, ps.Items...)
case CronJob:
jobs, err := k8sAPI.BatchV1().CronJobs(namespace).List(ctx, selector)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get cronjobs: %s", err)
os.Exit(1)
}
for _, job := range jobs.Items {
ps, err := GetPodsFor(ctx, k8sAPI, namespace, fmt.Sprintf("%s/%s", CronJob, job.Name))
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get pods: %s", err)
os.Exit(1)
}
pods = append(pods, ps...)
}
case DaemonSet:
dss, err := k8sAPI.AppsV1().DaemonSets(namespace).List(ctx, selector)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get demonsets: %s", err)
os.Exit(1)
}
for _, ds := range dss.Items {
ps, err := GetPodsFor(ctx, k8sAPI, namespace, fmt.Sprintf("%s/%s", DaemonSet, ds.Name))
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get pods: %s", err)
os.Exit(1)
}
pods = append(pods, ps...)
}
case Deployment:
deploys, err := k8sAPI.AppsV1().Deployments(namespace).List(ctx, selector)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get deployments: %s", err)
os.Exit(1)
}
for _, deploy := range deploys.Items {
ps, err := GetPodsFor(ctx, k8sAPI, namespace, fmt.Sprintf("%s/%s", Deployment, deploy.Name))
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get pods: %s", err)
os.Exit(1)
}
pods = append(pods, ps...)
}
case Job:
jobs, err := k8sAPI.BatchV1().Jobs(namespace).List(ctx, selector)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get jobs: %s", err)
os.Exit(1)
}
for _, job := range jobs.Items {
ps, err := GetPodsFor(ctx, k8sAPI, namespace, fmt.Sprintf("%s/%s", Job, job.Name))
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get pods: %s", err)
os.Exit(1)
}
pods = append(pods, ps...)
}
case ReplicaSet:
rss, err := k8sAPI.AppsV1().ReplicaSets(namespace).List(ctx, selector)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get replicasets: %s", err)
os.Exit(1)
}
for _, rs := range rss.Items {
ps, err := GetPodsFor(ctx, k8sAPI, namespace, fmt.Sprintf("%s/%s", ReplicaSet, rs.Name))
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get pods: %s", err)
os.Exit(1)
}
pods = append(pods, ps...)
}
case ReplicationController:
rcs, err := k8sAPI.CoreV1().ReplicationControllers(namespace).List(ctx, selector)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get replicationcontrollers: %s", err)
os.Exit(1)
}
for _, rc := range rcs.Items {
ps, err := GetPodsFor(ctx, k8sAPI, namespace, fmt.Sprintf("%s/%s", ReplicationController, rc.Name))
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get pods: %s", err)
os.Exit(1)
}
pods = append(pods, ps...)
}
case StatefulSet:
sss, err := k8sAPI.AppsV1().StatefulSets(namespace).List(ctx, selector)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get statefulsets: %s", err)
os.Exit(1)
}
for _, ss := range sss.Items {
ps, err := GetPodsFor(ctx, k8sAPI, namespace, fmt.Sprintf("%s/%s", StatefulSet, ss.Name))
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get pods: %s", err)
os.Exit(1)
}
pods = append(pods, ps...)
}
default:
return nil, fmt.Errorf("unsupported resource type: %s", typ)
}
return pods, nil
}
package k8s
import (
"context"
"fmt"
"io"
"net"
"net/http"
"net/url"
"os"
"strconv"
log "github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/portforward"
"k8s.io/client-go/transport/spdy"
// Load all the auth plugins for the cloud providers.
_ "k8s.io/client-go/plugin/pkg/client/auth"
)
// PortForward provides a port-forward connection into a Kubernetes cluster.
type PortForward struct {
method string
url *url.URL
host string
namespace string
podName string
localPort int
remotePort int
emitLogs bool
stopCh chan struct{}
readyCh chan struct{}
config *rest.Config
}
// NewContainerMetricsForward returns an instance of the PortForward struct that can
// be used to establish a port-forward connection to a containers metrics
// endpoint, specified by namespace, pod, container and portName.
func NewContainerMetricsForward(
k8sAPI *KubernetesAPI,
pod corev1.Pod,
container corev1.Container,
emitLogs bool,
portName string,
) (*PortForward, error) {
var port corev1.ContainerPort
for _, p := range container.Ports {
if p.Name == portName {
port = p
break
}
}
if port.Name != portName {
return nil, fmt.Errorf("no %s port found for container %s/%s", portName, pod.GetName(), container.Name)
}
return NewPodPortForward(k8sAPI, pod.GetNamespace(), pod.GetName(), "localhost", 0, int(port.ContainerPort), emitLogs)
}
// NewPortForward returns an instance of the PortForward struct that can be used
// to establish a port-forward connection to a pod in the deployment that's
// specified by namespace and deployName. If localPort is 0, it will use a
// random ephemeral port.
// Note that the connection remains open for the life of the process, as this
// function is typically called by the CLI. Care should be taken if called from
// control plane code.
func NewPortForward(
ctx context.Context,
k8sAPI *KubernetesAPI,
namespace, deployName string,
host string, localPort, remotePort int,
emitLogs bool,
) (*PortForward, error) {
timeoutSeconds := int64(30)
podList, err := k8sAPI.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{TimeoutSeconds: &timeoutSeconds})
if err != nil {
return nil, err
}
podName := ""
for _, pod := range podList.Items {
if pod.Status.Phase == corev1.PodRunning {
grandparent, err := getDeploymentForPod(ctx, k8sAPI, pod)
if err != nil {
log.Warnf("Failed to get deploy for pod [%s]: %s", pod.Name, err)
continue
}
if grandparent == deployName {
podName = pod.Name
break
}
}
}
if podName == "" {
return nil, fmt.Errorf("no running pods found for %s", deployName)
}
return NewPodPortForward(k8sAPI, namespace, podName, host, localPort, remotePort, emitLogs)
}
func getDeploymentForPod(ctx context.Context, k8sAPI *KubernetesAPI, pod corev1.Pod) (string, error) {
parents := pod.GetOwnerReferences()
if len(parents) != 1 {
return "", nil
}
rs, err := k8sAPI.AppsV1().ReplicaSets(pod.Namespace).Get(ctx, parents[0].Name, metav1.GetOptions{})
if err != nil {
return "", err
}
grandparents := rs.GetOwnerReferences()
if len(grandparents) != 1 {
return "", nil
}
return grandparents[0].Name, nil
}
// NewPodPortForward returns an instance of the PortForward struct that can be
// used to establish a port-forward connection to a specific Pod.
func NewPodPortForward(
k8sAPI *KubernetesAPI,
namespace, podName string,
host string, localPort, remotePort int,
emitLogs bool,
) (*PortForward, error) {
restClient := k8sAPI.CoreV1().RESTClient()
// This early return is for testing purposes. If the k8sAPI is a fake
// client, attempting to create a request will result in a nil-pointer
// panic. Instead, we return with no port-forward and no error.
if fakeRest, ok := restClient.(*rest.RESTClient); ok {
if fakeRest == nil {
return nil, nil
}
}
req := restClient.Post().
Resource("pods").
Namespace(namespace).
Name(podName).
SubResource("portforward")
var err error
if localPort == 0 {
if host != "localhost" {
return nil, fmt.Errorf("local port must be specified when host is not localhost")
}
localPort, err = getEphemeralPort()
if err != nil {
return nil, err
}
}
return &PortForward{
method: "POST",
url: req.URL(),
host: host,
namespace: namespace,
podName: podName,
localPort: localPort,
remotePort: remotePort,
emitLogs: emitLogs,
stopCh: make(chan struct{}, 1),
readyCh: make(chan struct{}),
config: k8sAPI.Config,
}, nil
}
// run creates and runs the port-forward connection.
// When the connection is established it blocks until Stop() is called.
func (pf *PortForward) run() error {
transport, upgrader, err := spdy.RoundTripperFor(pf.config)
if err != nil {
return err
}
out := io.Discard
errOut := io.Discard
if pf.emitLogs {
out = os.Stdout
errOut = os.Stderr
}
ports := []string{fmt.Sprintf("%d:%d", pf.localPort, pf.remotePort)}
dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, pf.method, pf.url)
fw, err := portforward.NewOnAddresses(dialer, []string{pf.host}, ports, pf.stopCh, pf.readyCh, out, errOut)
if err != nil {
return err
}
err = fw.ForwardPorts()
if err != nil {
err = fmt.Errorf("%w for %s/%s", err, pf.namespace, pf.podName)
return err
}
return nil
}
// Init creates and runs a port-forward connection.
// This function blocks until the connection is established, in which case it returns nil.
// It's the caller's responsibility to call Stop() to finish the connection.
func (pf *PortForward) Init() error {
log.Debugf("Starting port forward to %s %d:%d", pf.url, pf.localPort, pf.remotePort)
failure := make(chan error, 1)
go func() {
if err := pf.run(); err != nil {
failure <- err
}
}()
// The `select` statement below depends on one of two outcomes from `pf.run()`:
// 1) Succeed and block, causing a receive on `<-pf.readyCh`
// 2) Return an err, causing a receive `<-failure`
select {
case <-pf.readyCh:
log.Debug("Port forward initialised")
case err := <-failure:
log.Debugf("Port forward failed: %v", err)
return err
}
return nil
}
// Stop terminates the port-forward connection.
// It is the caller's responsibility to call Stop even in case of errors
func (pf *PortForward) Stop() {
close(pf.stopCh)
}
// GetStop returns the stopCh.
// Receiving on stopCh will block until the port forwarding stops.
func (pf *PortForward) GetStop() <-chan struct{} {
return pf.stopCh
}
// URLFor returns the URL for the port-forward connection.
func (pf *PortForward) URLFor(path string) string {
strPort := strconv.Itoa(pf.localPort)
urlAddress := net.JoinHostPort(pf.host, strPort)
return fmt.Sprintf("http://%s%s", urlAddress, path)
}
// AddressAndPort returns the address and port for the port-forward connection.
func (pf *PortForward) AddressAndPort() string {
strPort := strconv.Itoa(pf.localPort)
return net.JoinHostPort(pf.host, strPort)
}
// getEphemeralPort selects a port for the port-forwarding. It binds to a free
// ephemeral port and returns the port number.
func getEphemeralPort() (int, error) {
ln, err := net.Listen("tcp", "localhost:0")
if err != nil {
return 0, err
}
defer ln.Close()
// get port
tcpAddr, ok := ln.Addr().(*net.TCPAddr)
if !ok {
return 0, fmt.Errorf("invalid listen address: %s", ln.Addr())
}
return tcpAddr.Port, nil
}
package resource
import (
"context"
"encoding/json"
"fmt"
"io"
link "github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha1"
policy "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1"
profile "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
"github.com/linkerd/linkerd2/pkg/k8s"
log "github.com/sirupsen/logrus"
admissionRegistration "k8s.io/api/admissionregistration/v1"
apps "k8s.io/api/apps/v1"
batch "k8s.io/api/batch/v1"
core "k8s.io/api/core/v1"
k8sPolicy "k8s.io/api/policy/v1"
rbac "k8s.io/api/rbac/v1"
apiextension "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
apiRegistration "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
apiregistrationv1client "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1"
"sigs.k8s.io/yaml"
)
const (
yamlSep = "---\n"
)
// Kubernetes is a parent object used to generalize all k8s types
type Kubernetes struct {
runtime.TypeMeta
metav1.ObjectMeta `json:"metadata"`
}
var prunableNamespaceResources []schema.GroupVersionResource = []schema.GroupVersionResource{
core.SchemeGroupVersion.WithResource("configmaps"),
batch.SchemeGroupVersion.WithResource("cronjobs"),
apps.SchemeGroupVersion.WithResource("daemonsets"),
apps.SchemeGroupVersion.WithResource("deployments"),
batch.SchemeGroupVersion.WithResource("jobs"),
policy.SchemeGroupVersion.WithResource("meshtlsauthentications"),
policy.SchemeGroupVersion.WithResource("networkauthentications"),
core.SchemeGroupVersion.WithResource("replicationcontrollers"),
core.SchemeGroupVersion.WithResource("secrets"),
core.SchemeGroupVersion.WithResource("services"),
profile.SchemeGroupVersion.WithResource("serviceprofiles"),
apps.SchemeGroupVersion.WithResource("statefulsets"),
rbac.SchemeGroupVersion.WithResource("roles"),
rbac.SchemeGroupVersion.WithResource("rolebindings"),
core.SchemeGroupVersion.WithResource("serviceaccounts"),
k8sPolicy.SchemeGroupVersion.WithResource("poddisruptionbudgets"),
k8s.ServerGVR,
k8s.SazGVR,
k8s.AuthorizationPolicyGVR,
link.SchemeGroupVersion.WithResource("links"),
k8s.HTTPRouteGVR,
}
var prunableClusterResources []schema.GroupVersionResource = []schema.GroupVersionResource{
rbac.SchemeGroupVersion.WithResource("clusterroles"),
rbac.SchemeGroupVersion.WithResource("clusterrolebindings"),
apiRegistration.SchemeGroupVersion.WithResource("apiservices"),
admissionRegistration.SchemeGroupVersion.WithResource("mutatingwebhookconfigurations"),
admissionRegistration.SchemeGroupVersion.WithResource("validatingwebhookconfigurations"),
apiextension.SchemeGroupVersion.WithResource("customresourcedefinitions"),
}
// New returns a kubernetes resource with the given data
func New(apiVersion, kind, name string) Kubernetes {
return Kubernetes{
runtime.TypeMeta{
APIVersion: apiVersion,
Kind: kind,
},
metav1.ObjectMeta{
Name: name,
},
}
}
// NewNamespaced returns a namespace scoped kubernetes resource with the given data
func NewNamespaced(apiVersion, kind, name, namespace string) Kubernetes {
return Kubernetes{
runtime.TypeMeta{
APIVersion: apiVersion,
Kind: kind,
},
metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
}
}
// RenderResource renders a kubernetes object as a yaml object
func (r Kubernetes) RenderResource(w io.Writer) error {
b, err := yaml.Marshal(r)
if err != nil {
return err
}
_, err = w.Write(b)
if err != nil {
return err
}
_, err = w.Write([]byte(yamlSep))
return err
}
// RenderResource renders a kubernetes object as a json object
func (r Kubernetes) RenderResourceJSON(w io.Writer) error {
b, err := json.Marshal(r)
if err != nil {
return err
}
_, err = w.Write(b)
if err != nil {
return err
}
_, err = w.Write([]byte("\n"))
return err
}
// FetchKubernetesResources returns a slice of all cluster scoped kubernetes
// resources which match the given ListOptions.
func FetchKubernetesResources(ctx context.Context, k *k8s.KubernetesAPI, options metav1.ListOptions) ([]Kubernetes, error) {
resources := make([]Kubernetes, 0)
clusterRoles, err := fetchClusterRoles(ctx, k, options)
if err != nil {
return nil, fmt.Errorf("could not fetch ClusterRole resources: %w", err)
}
resources = append(resources, clusterRoles...)
clusterRoleBindings, err := fetchClusterRoleBindings(ctx, k, options)
if err != nil {
return nil, fmt.Errorf("could not fetch ClusterRoleBinding resources: %w", err)
}
resources = append(resources, clusterRoleBindings...)
roles, err := fetchRoles(ctx, k, options)
if err != nil {
return nil, fmt.Errorf("could not fetch Roles: %w", err)
}
resources = append(resources, roles...)
roleBindings, err := fetchRoleBindings(ctx, k, options)
if err != nil {
return nil, fmt.Errorf("could not fetch RoleBindings: %w", err)
}
resources = append(resources, roleBindings...)
crds, err := fetchCustomResourceDefinitions(ctx, k, options)
if err != nil {
return nil, fmt.Errorf("could not fetch CustomResourceDefinition resources: %w", err)
}
resources = append(resources, crds...)
apiCRDs, err := fetchAPIRegistrationResources(ctx, k, options)
if err != nil {
return nil, fmt.Errorf("could not fetch APIService CRDs: %w", err)
}
resources = append(resources, apiCRDs...)
mutatinghooks, err := fetchMutatingWebhooksConfiguration(ctx, k, options)
if err != nil {
return nil, fmt.Errorf("could not fetch MutatingWebhookConfigurations: %w", err)
}
resources = append(resources, mutatinghooks...)
validationhooks, err := fetchValidatingWebhooksConfiguration(ctx, k, options)
if err != nil {
return nil, fmt.Errorf("could not fetch ValidatingWebhookConfiguration: %w", err)
}
resources = append(resources, validationhooks...)
namespaces, err := fetchNamespace(ctx, k, options)
if err != nil {
return nil, fmt.Errorf("could not fetch Namespace: %w", err)
}
resources = append(resources, namespaces...)
return resources, nil
}
func fetchClusterRoles(ctx context.Context, k *k8s.KubernetesAPI, options metav1.ListOptions) ([]Kubernetes, error) {
list, err := k.RbacV1().ClusterRoles().List(ctx, options)
if err != nil {
return nil, err
}
resources := make([]Kubernetes, len(list.Items))
for i, item := range list.Items {
resources[i] = New(rbac.SchemeGroupVersion.String(), "ClusterRole", item.Name)
}
return resources, nil
}
func fetchClusterRoleBindings(ctx context.Context, k *k8s.KubernetesAPI, options metav1.ListOptions) ([]Kubernetes, error) {
list, err := k.RbacV1().ClusterRoleBindings().List(ctx, options)
if err != nil {
return nil, err
}
resources := make([]Kubernetes, len(list.Items))
for i, item := range list.Items {
resources[i] = New(rbac.SchemeGroupVersion.String(), "ClusterRoleBinding", item.Name)
}
return resources, nil
}
func fetchRoles(ctx context.Context, k *k8s.KubernetesAPI, options metav1.ListOptions) ([]Kubernetes, error) {
list, err := k.RbacV1().Roles("").List(ctx, options)
if err != nil {
return nil, err
}
resources := make([]Kubernetes, len(list.Items))
for i, item := range list.Items {
r := New(rbac.SchemeGroupVersion.String(), "Role", item.Name)
r.Namespace = item.Namespace
resources[i] = r
}
return resources, nil
}
func fetchRoleBindings(ctx context.Context, k *k8s.KubernetesAPI, options metav1.ListOptions) ([]Kubernetes, error) {
list, err := k.RbacV1().RoleBindings("").List(ctx, options)
if err != nil {
return nil, err
}
resources := make([]Kubernetes, len(list.Items))
for i, item := range list.Items {
r := New(rbac.SchemeGroupVersion.String(), "RoleBinding", item.Name)
r.Namespace = item.Namespace
resources[i] = r
}
return resources, nil
}
func fetchCustomResourceDefinitions(ctx context.Context, k *k8s.KubernetesAPI, options metav1.ListOptions) ([]Kubernetes, error) {
list, err := k.Apiextensions.ApiextensionsV1().CustomResourceDefinitions().List(ctx, options)
if err != nil {
return nil, err
}
resources := make([]Kubernetes, len(list.Items))
for i, item := range list.Items {
resources[i] = New(apiextension.SchemeGroupVersion.String(), "CustomResourceDefinition", item.Name)
}
return resources, nil
}
func fetchNamespace(ctx context.Context, k *k8s.KubernetesAPI, options metav1.ListOptions) ([]Kubernetes, error) {
list, err := k.CoreV1().Namespaces().List(ctx, options)
if err != nil {
return nil, err
}
resources := make([]Kubernetes, len(list.Items))
for i, item := range list.Items {
r := New(core.SchemeGroupVersion.String(), "Namespace", item.Name)
r.Namespace = item.Namespace
resources[i] = r
}
return resources, nil
}
func fetchValidatingWebhooksConfiguration(ctx context.Context, k *k8s.KubernetesAPI, options metav1.ListOptions) ([]Kubernetes, error) {
list, err := k.AdmissionregistrationV1().ValidatingWebhookConfigurations().List(ctx, options)
if err != nil {
return nil, err
}
resources := make([]Kubernetes, len(list.Items))
for i, item := range list.Items {
resources[i] = New(admissionRegistration.SchemeGroupVersion.String(), "ValidatingWebhookConfiguration", item.Name)
}
return resources, nil
}
func fetchMutatingWebhooksConfiguration(ctx context.Context, k *k8s.KubernetesAPI, options metav1.ListOptions) ([]Kubernetes, error) {
list, err := k.AdmissionregistrationV1().MutatingWebhookConfigurations().List(ctx, options)
if err != nil {
return nil, err
}
resources := make([]Kubernetes, len(list.Items))
for i, item := range list.Items {
resources[i] = New(admissionRegistration.SchemeGroupVersion.String(), "MutatingWebhookConfiguration", item.Name)
}
return resources, nil
}
func fetchAPIRegistrationResources(ctx context.Context, k *k8s.KubernetesAPI, options metav1.ListOptions) ([]Kubernetes, error) {
apiClient, err := apiregistrationv1client.NewForConfig(k.Config)
if err != nil {
return nil, err
}
list, err := apiClient.APIServices().List(ctx, options)
if err != nil {
return nil, err
}
resources := make([]Kubernetes, len(list.Items))
for i, item := range list.Items {
resources[i] = New(apiRegistration.SchemeGroupVersion.String(), "APIService", item.Name)
}
return resources, nil
}
func FetchPrunableResources(ctx context.Context, k *k8s.KubernetesAPI, namespace string, options metav1.ListOptions) ([]Kubernetes, error) {
resources := []Kubernetes{}
for _, gvr := range prunableNamespaceResources {
items, err := k.DynamicClient.Resource(gvr).Namespace(namespace).List(ctx, options)
if err != nil {
if !kerrors.IsNotFound(err) {
log.Debugf("failed to list resources of type %s", gvr)
}
continue
}
for _, item := range items.Items {
resources = append(resources, NewNamespaced(item.GetAPIVersion(), item.GetKind(), item.GetName(), item.GetNamespace()))
}
}
for _, gvr := range prunableClusterResources {
items, err := k.DynamicClient.Resource(gvr).List(ctx, options)
if err != nil {
log.Debugf("failed to list resources of type %s", gvr)
continue
}
for _, item := range items.Items {
resources = append(resources, New(item.GetAPIVersion(), item.GetKind(), item.GetName()))
}
}
return resources, nil
}
package k8s
import (
"fmt"
"regexp"
"strconv"
"strings"
)
var revisionSeparator = regexp.MustCompile("[^0-9.]")
func getK8sVersion(versionString string) ([3]int, error) {
var version [3]int
justTheVersionString := strings.TrimPrefix(versionString, "v")
justTheMajorMinorRevisionNumbers := revisionSeparator.Split(justTheVersionString, -1)[0]
split := strings.Split(justTheMajorMinorRevisionNumbers, ".")
if len(split) < 3 {
return version, fmt.Errorf("unknown version string format [%s]", versionString)
}
for i, segment := range split {
v, err := strconv.Atoi(strings.TrimSpace(segment))
if err != nil {
return version, fmt.Errorf("unknown version string format [%s]", versionString)
}
version[i] = v
}
return version, nil
}
func isCompatibleVersion(minimalRequirementVersion [3]int, actualVersion [3]int) bool {
if minimalRequirementVersion[0] < actualVersion[0] {
return true
}
if (minimalRequirementVersion[0] == actualVersion[0]) && minimalRequirementVersion[1] < actualVersion[1] {
return true
}
if (minimalRequirementVersion[0] == actualVersion[0]) && (minimalRequirementVersion[1] == actualVersion[1]) && (minimalRequirementVersion[2] <= actualVersion[2]) {
return true
}
return false
}
package profiles
import (
"fmt"
"io"
"net/http"
"sort"
"strings"
"github.com/go-openapi/spec"
sp "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
)
const (
xLinkerdRetryable = "x-linkerd-retryable"
xLinkerdTimeout = "x-linkerd-timeout"
)
// RenderOpenAPI reads an OpenAPI spec file and renders the corresponding
// ServiceProfile to a buffer, given a namespace, service, and control plane
// namespace.
func RenderOpenAPI(fileName, namespace, name, clusterDomain string) (*sp.ServiceProfile, error) {
input, err := readFile(fileName)
if err != nil {
return nil, err
}
bytes, err := io.ReadAll(input)
if err != nil {
return nil, fmt.Errorf("Error reading file: %w", err)
}
json, err := yaml.YAMLToJSON(bytes)
if err != nil {
return nil, fmt.Errorf("Error parsing yaml: %w", err)
}
swagger := spec.Swagger{}
err = swagger.UnmarshalJSON(json)
if err != nil {
return nil, fmt.Errorf("Error parsing OpenAPI spec: %w", err)
}
profile := swaggerToServiceProfile(swagger, namespace, name, clusterDomain)
return &profile, nil
}
func swaggerToServiceProfile(swagger spec.Swagger, namespace, name, clusterDomain string) sp.ServiceProfile {
profile := sp.ServiceProfile{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%s.%s.svc.%s", name, namespace, clusterDomain),
Namespace: namespace,
},
TypeMeta: ServiceProfileMeta,
}
routes := make([]*sp.RouteSpec, 0)
paths := make([]string, 0)
if swagger.Paths != nil {
for path := range swagger.Paths.Paths {
paths = append(paths, path)
}
sort.Strings(paths)
}
base := strings.TrimRight(swagger.BasePath, "/")
for _, relPath := range paths {
item := swagger.Paths.Paths[relPath]
path := base + "/" + strings.TrimLeft(relPath, "/")
pathRegex := PathToRegex(path)
if item.Delete != nil {
spec := MkRouteSpec(path, pathRegex, http.MethodDelete, item.Delete)
routes = append(routes, spec)
}
if item.Get != nil {
spec := MkRouteSpec(path, pathRegex, http.MethodGet, item.Get)
routes = append(routes, spec)
}
if item.Head != nil {
spec := MkRouteSpec(path, pathRegex, http.MethodHead, item.Head)
routes = append(routes, spec)
}
if item.Options != nil {
spec := MkRouteSpec(path, pathRegex, http.MethodOptions, item.Options)
routes = append(routes, spec)
}
if item.Patch != nil {
spec := MkRouteSpec(path, pathRegex, http.MethodPatch, item.Patch)
routes = append(routes, spec)
}
if item.Post != nil {
spec := MkRouteSpec(path, pathRegex, http.MethodPost, item.Post)
routes = append(routes, spec)
}
if item.Put != nil {
spec := MkRouteSpec(path, pathRegex, http.MethodPut, item.Put)
routes = append(routes, spec)
}
}
profile.Spec.Routes = routes
return profile
}
// MkRouteSpec makes a service profile route from an OpenAPI operation.
func MkRouteSpec(path, pathRegex string, method string, operation *spec.Operation) *sp.RouteSpec {
retryable := false
timeout := ""
var responses *spec.Responses
if operation != nil {
retryable, _ = operation.VendorExtensible.Extensions.GetBool(xLinkerdRetryable)
timeout, _ = operation.VendorExtensible.Extensions.GetString(xLinkerdTimeout)
responses = operation.Responses
}
return &sp.RouteSpec{
Name: fmt.Sprintf("%s %s", method, path),
Condition: toReqMatch(pathRegex, method),
ResponseClasses: toRspClasses(responses),
IsRetryable: retryable,
Timeout: timeout,
}
}
func toReqMatch(path string, method string) *sp.RequestMatch {
return &sp.RequestMatch{
PathRegex: path,
Method: method,
}
}
func toRspClasses(responses *spec.Responses) []*sp.ResponseClass {
if responses == nil {
return nil
}
classes := make([]*sp.ResponseClass, 0)
statuses := make([]int, 0)
for status := range responses.StatusCodeResponses {
statuses = append(statuses, status)
}
sort.Ints(statuses)
for _, status := range statuses {
cond := &sp.ResponseMatch{
Status: &sp.Range{
Min: uint32(status),
Max: uint32(status),
},
}
classes = append(classes, &sp.ResponseClass{
Condition: cond,
IsFailure: status >= 500,
})
}
return classes
}
package profiles
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"text/template"
"time"
sp "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2" // TODO: pkg/profiles should not depend on controller/gen
pkgcmd "github.com/linkerd/linkerd2/pkg/cmd"
"github.com/linkerd/linkerd2/pkg/k8s"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/validation"
yamlDecoder "k8s.io/apimachinery/pkg/util/yaml"
"sigs.k8s.io/yaml"
)
var pathParamRegex = regexp.MustCompile(`\\{[^\}]*\\}`)
type profileTemplateConfig struct {
ServiceNamespace string
ServiceName string
ClusterDomain string
}
var (
// ServiceProfileMeta is the TypeMeta for the ServiceProfile custom resource.
ServiceProfileMeta = metav1.TypeMeta{
APIVersion: k8s.ServiceProfileAPIVersion,
Kind: k8s.ServiceProfileKind,
}
minStatus uint32 = 100
maxStatus uint32 = 599
errRequestMatchField = errors.New("A request match must have a field set")
errResponseMatchField = errors.New("A response match must have a field set")
)
// Validate validates the structure of a ServiceProfile. This code is a superset
// of the validation provided by the `openAPIV3Schema`, defined in the
// ServiceProfile CRD.
// openAPIV3Schema validates:
// - types of non-recursive fields
// - presence of required fields
// This function validates:
// - types of all fields
// - presence of required fields
// - presence of unknown fields
// - recursive fields
func Validate(data []byte) error {
var serviceProfile sp.ServiceProfile
err := yaml.UnmarshalStrict(data, &serviceProfile)
if err != nil {
return fmt.Errorf("failed to validate ServiceProfile: %w", err)
}
errs := validation.IsDNS1123Subdomain(serviceProfile.Name)
if len(errs) > 0 {
return fmt.Errorf("ServiceProfile %q has invalid name: %s", serviceProfile.Name, errs[0])
}
for _, route := range serviceProfile.Spec.Routes {
if route.Name == "" {
return fmt.Errorf("ServiceProfile %q has a route with no name", serviceProfile.Name)
}
if route.Timeout != "" {
_, err := time.ParseDuration(route.Timeout)
if err != nil {
return fmt.Errorf("ServiceProfile %q has a route with an invalid timeout: %w", serviceProfile.Name, err)
}
}
if route.Condition == nil {
return fmt.Errorf("ServiceProfile %q has a route with no condition", serviceProfile.Name)
}
err := ValidateRequestMatch(route.Condition)
if err != nil {
return fmt.Errorf("ServiceProfile %q has a route with an invalid condition: %w", serviceProfile.Name, err)
}
for _, rc := range route.ResponseClasses {
if rc.Condition == nil {
return fmt.Errorf("ServiceProfile %q has a response class with no condition", serviceProfile.Name)
}
err = ValidateResponseMatch(rc.Condition)
if err != nil {
return fmt.Errorf("ServiceProfile %q has a response class with an invalid condition: %w", serviceProfile.Name, err)
}
}
}
rb := serviceProfile.Spec.RetryBudget
if rb != nil {
if rb.RetryRatio < 0 {
return fmt.Errorf("ServiceProfile %q RetryBudget RetryRatio must be non-negative: %f", serviceProfile.Name, rb.RetryRatio)
}
if rb.TTL == "" {
return fmt.Errorf("ServiceProfile %q RetryBudget missing TTL field", serviceProfile.Name)
}
_, err := time.ParseDuration(rb.TTL)
if err != nil {
return fmt.Errorf("ServiceProfile %q RetryBudget: %w", serviceProfile.Name, err)
}
}
return nil
}
// ValidateRequestMatch validates whether a ServiceProfile RequestMatch has at
// least one field set.
func ValidateRequestMatch(reqMatch *sp.RequestMatch) error {
matchKindSet := false
if reqMatch.All != nil {
matchKindSet = true
for _, child := range reqMatch.All {
err := ValidateRequestMatch(child)
if err != nil {
return err
}
}
}
if reqMatch.Any != nil {
matchKindSet = true
for _, child := range reqMatch.Any {
err := ValidateRequestMatch(child)
if err != nil {
return err
}
}
}
if reqMatch.Method != "" {
matchKindSet = true
}
if reqMatch.Not != nil {
matchKindSet = true
err := ValidateRequestMatch(reqMatch.Not)
if err != nil {
return err
}
}
if reqMatch.PathRegex != "" {
matchKindSet = true
}
if !matchKindSet {
return errRequestMatchField
}
return nil
}
// ValidateResponseMatch validates whether a ServiceProfile ResponseMatch has at
// least one field set, and sanity checks the Status Range.
func ValidateResponseMatch(rspMatch *sp.ResponseMatch) error {
matchKindSet := false
if rspMatch.All != nil {
matchKindSet = true
for _, child := range rspMatch.All {
err := ValidateResponseMatch(child)
if err != nil {
return err
}
}
}
if rspMatch.Any != nil {
matchKindSet = true
for _, child := range rspMatch.Any {
err := ValidateResponseMatch(child)
if err != nil {
return err
}
}
}
if rspMatch.Status != nil {
if rspMatch.Status.Min != 0 && (rspMatch.Status.Min < minStatus || rspMatch.Status.Min > maxStatus) {
return fmt.Errorf("Range minimum must be between %d and %d, inclusive", minStatus, maxStatus)
} else if rspMatch.Status.Max != 0 && (rspMatch.Status.Max < minStatus || rspMatch.Status.Max > maxStatus) {
return fmt.Errorf("Range maximum must be between %d and %d, inclusive", minStatus, maxStatus)
} else if rspMatch.Status.Max != 0 && rspMatch.Status.Min != 0 && rspMatch.Status.Max < rspMatch.Status.Min {
return errors.New("Range maximum cannot be smaller than minimum")
}
matchKindSet = true
}
if rspMatch.Not != nil {
matchKindSet = true
err := ValidateResponseMatch(rspMatch.Not)
if err != nil {
return err
}
}
if !matchKindSet {
return errResponseMatchField
}
return nil
}
func buildConfig(namespace, service, clusterDomain string) *profileTemplateConfig {
return &profileTemplateConfig{
ServiceNamespace: namespace,
ServiceName: service,
ClusterDomain: clusterDomain,
}
}
// RenderProfileTemplate renders a ServiceProfile template to a buffer, given a
// namespace, service, and control plane namespace.
func RenderProfileTemplate(namespace, service, clusterDomain string, w io.Writer, format string) error {
config := buildConfig(namespace, service, clusterDomain)
template, err := template.New("profile").Parse(Template)
if err != nil {
return err
}
buf := &bytes.Buffer{}
err = template.Execute(buf, config)
if err != nil {
return err
}
if format == pkgcmd.JsonOutput {
bytes, err := yamlDecoder.ToJSON(buf.Bytes())
if err != nil {
return err
}
_, err = w.Write(append(bytes, '\n'))
return err
}
if format == pkgcmd.YamlOutput {
_, err = w.Write(buf.Bytes())
return err
}
return fmt.Errorf("unknown output format: %s", format)
}
func readFile(fileName string) (io.Reader, error) {
if fileName == "-" {
return os.Stdin, nil
}
return os.Open(filepath.Clean(fileName))
}
// PathToRegex converts a path into a regex.
func PathToRegex(path string) string {
escaped := regexp.QuoteMeta(path)
return pathParamRegex.ReplaceAllLiteralString(escaped, "[^/]*")
}
package profiles
import (
"os"
fuzz "github.com/AdaLogics/go-fuzz-headers"
)
// FuzzProfilesValidate fuzzes the ProfilesValidate function.
func FuzzProfilesValidate(data []byte) int {
_ = Validate(data)
return 1
}
// FuzzRenderProto fuzzes the RenderProto function.
func FuzzRenderProto(data []byte) int {
f := fuzz.NewConsumer(data)
protodata, err := f.GetBytes()
if err != nil {
return 0
}
namespace, err := f.GetString()
if err != nil {
return 0
}
name, err := f.GetString()
if err != nil {
return 0
}
clusterDomain, err := f.GetString()
if err != nil {
return 0
}
protofile, err := os.Create("protofile")
if err != nil {
return 0
}
defer protofile.Close()
defer os.Remove(protofile.Name())
_, err = protofile.Write(protodata)
if err != nil {
return 0
}
_, err = RenderProto(protofile.Name(), namespace, name, clusterDomain)
if err != nil {
return 0
}
return 1
}
package profiles
import (
"fmt"
"net/http"
"regexp"
"strings"
"github.com/emicklei/proto"
sp "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// RenderProto reads a protobuf definition file and renders the corresponding
// ServiceProfile to a buffer, given a namespace, service, and control plane
// namespace.
func RenderProto(fileName, namespace, name, clusterDomain string) (*sp.ServiceProfile, error) {
input, err := readFile(fileName)
if err != nil {
return nil, err
}
parser := proto.NewParser(input)
return protoToServiceProfile(parser, namespace, name, clusterDomain)
}
func protoToServiceProfile(parser *proto.Parser, namespace, name, clusterDomain string) (*sp.ServiceProfile, error) {
definition, err := parser.Parse()
if err != nil {
return nil, err
}
routes := make([]*sp.RouteSpec, 0)
pkg := ""
handle := func(visitee proto.Visitee) {
switch typed := visitee.(type) {
case *proto.Package:
pkg = typed.Name
case *proto.RPC:
if service, ok := typed.Parent.(*proto.Service); ok {
var path string
switch pkg {
case "":
path = fmt.Sprintf("/%s/%s", service.Name, typed.Name)
default:
path = fmt.Sprintf("/%s.%s/%s", pkg, service.Name, typed.Name)
}
route := &sp.RouteSpec{
Name: typed.Name,
Condition: &sp.RequestMatch{
Method: http.MethodPost,
PathRegex: regexp.QuoteMeta(path),
},
IsRetryable: isMethodRetryable(typed),
}
routes = append(routes, route)
}
}
}
proto.Walk(definition, handle)
return &sp.ServiceProfile{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%s.%s.svc.%s", name, namespace, clusterDomain),
Namespace: namespace,
},
TypeMeta: ServiceProfileMeta,
Spec: sp.ServiceProfileSpec{
Routes: routes,
},
}, nil
}
func isMethodRetryable(rpc *proto.RPC) bool {
for _, e := range rpc.Elements {
option, ok := e.(*proto.Option)
if !ok {
// Not an option
continue
}
// method options can be wrapped in parentheses so we trim them away
if strings.Trim(option.Name, "()") != "idempotency_level" {
// Not an idempotency option
continue
}
return option.Constant.Source == "IDEMPOTENT" ||
option.Constant.Source == "NO_SIDE_EFFECTS"
}
return false
}
package profiles
import (
"fmt"
"github.com/go-test/deep"
"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// GenServiceProfile generates a mock ServiceProfile.
func GenServiceProfile(service, namespace, clusterDomain string) v1alpha2.ServiceProfile {
return v1alpha2.ServiceProfile{
TypeMeta: ServiceProfileMeta,
ObjectMeta: metav1.ObjectMeta{
Name: service + "." + namespace + ".svc." + clusterDomain,
Namespace: namespace,
},
Spec: v1alpha2.ServiceProfileSpec{
Routes: []*v1alpha2.RouteSpec{
{
Name: "/authors/{id}",
Condition: &v1alpha2.RequestMatch{
PathRegex: "/authors/\\d+",
Method: "POST",
},
ResponseClasses: []*v1alpha2.ResponseClass{
{
Condition: &v1alpha2.ResponseMatch{
Status: &v1alpha2.Range{
Min: 500,
Max: 599,
},
},
IsFailure: true,
},
},
},
},
},
}
}
// ServiceProfileYamlEquals validates whether two ServiceProfiles are equal.
func ServiceProfileYamlEquals(actual, expected v1alpha2.ServiceProfile) error {
if diff := deep.Equal(actual, expected); diff != nil {
return fmt.Errorf("ServiceProfile mismatch: %+v", diff)
}
return nil
}
package prometheus
import (
"net/http"
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.opencensus.io/plugin/ocgrpc"
"go.opencensus.io/plugin/ochttp"
"google.golang.org/grpc"
)
var (
// RequestLatencyBucketsSeconds represents latency buckets to record (seconds)
RequestLatencyBucketsSeconds = append(append(append(
prometheus.LinearBuckets(0.01, 0.01, 5),
prometheus.LinearBuckets(0.1, 0.1, 5)...),
prometheus.LinearBuckets(1, 1, 5)...),
prometheus.LinearBuckets(10, 10, 5)...)
// ResponseSizeBuckets represents response size buckets (bytes)
ResponseSizeBuckets = append(append(append(
prometheus.LinearBuckets(100, 100, 5),
prometheus.LinearBuckets(1000, 1000, 5)...),
prometheus.LinearBuckets(10000, 10000, 5)...),
prometheus.LinearBuckets(1000000, 1000000, 5)...)
// server metrics
serverCounter = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_server_requests_total",
Help: "A counter for requests to the wrapped handler.",
},
[]string{"code", "method"},
)
serverLatency = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_server_request_latency_seconds",
Help: "A histogram of latencies for requests in seconds.",
Buckets: RequestLatencyBucketsSeconds,
},
[]string{"code", "method"},
)
serverResponseSize = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_server_response_size_bytes",
Help: "A histogram of response sizes for requests.",
Buckets: ResponseSizeBuckets,
},
[]string{"code", "method"},
)
// client metrics
clientCounter = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_client_requests_total",
Help: "A counter for requests from the wrapped client.",
},
[]string{"client", "code", "method"},
)
clientErrorCounter = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_client_errors_total",
Help: "A counter for errors from the wrapped client.",
},
[]string{"client", "method"},
)
clientLatency = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_client_request_latency_seconds",
Help: "A histogram of request latencies.",
Buckets: RequestLatencyBucketsSeconds,
},
[]string{"client", "code", "method"},
)
clientInFlight = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "http_client_in_flight_requests",
Help: "A gauge of in-flight requests for the wrapped client.",
},
[]string{"client"},
)
clientQPS = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "http_client_qps",
Help: "Max QPS used for the client config.",
},
[]string{"client"},
)
clientBurst = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "http_client_burst",
Help: "Burst used for the client config.",
},
[]string{"client"},
)
)
func init() {
prometheus.MustRegister(
serverCounter, serverLatency, serverResponseSize, clientCounter,
clientLatency, clientInFlight, clientQPS, clientBurst, clientErrorCounter,
)
}
// NewGrpcServer returns a grpc server pre-configured with prometheus interceptors and oc-grpc handler
func NewGrpcServer(opt ...grpc.ServerOption) *grpc.Server {
server := grpc.NewServer(
append([]grpc.ServerOption{
grpc.UnaryInterceptor(grpc_prometheus.UnaryServerInterceptor),
grpc.StreamInterceptor(grpc_prometheus.StreamServerInterceptor),
grpc.StatsHandler(&ocgrpc.ServerHandler{}),
}, opt...)...,
)
grpc_prometheus.EnableHandlingTimeHistogram()
grpc_prometheus.Register(server)
return server
}
// WithTelemetry instruments the HTTP server with prometheus and oc-http handler
func WithTelemetry(handler http.Handler) http.Handler {
return &ochttp.Handler{
Handler: promhttp.InstrumentHandlerDuration(serverLatency,
promhttp.InstrumentHandlerResponseSize(serverResponseSize,
promhttp.InstrumentHandlerCounter(serverCounter, handler))),
}
}
// ClientWithTelemetry instruments the HTTP client with prometheus
func ClientWithTelemetry(name string, wt func(http.RoundTripper) http.RoundTripper) func(http.RoundTripper) http.RoundTripper {
latency := clientLatency.MustCurryWith(prometheus.Labels{"client": name})
counter := clientCounter.MustCurryWith(prometheus.Labels{"client": name})
inFlight := clientInFlight.With(prometheus.Labels{"client": name})
errors := clientErrorCounter.MustCurryWith(prometheus.Labels{"client": name})
return func(rt http.RoundTripper) http.RoundTripper {
if wt != nil {
rt = wt(rt)
}
return InstrumentErrorCounter(errors,
promhttp.InstrumentRoundTripperInFlight(inFlight,
promhttp.InstrumentRoundTripperCounter(counter,
promhttp.InstrumentRoundTripperDuration(latency, rt),
),
),
)
}
}
func InstrumentErrorCounter(counter *prometheus.CounterVec, next http.RoundTripper) promhttp.RoundTripperFunc {
return func(r *http.Request) (*http.Response, error) {
resp, err := next.RoundTrip(r)
if err != nil {
counter.With(prometheus.Labels{"method": r.Method}).Inc()
}
return resp, err
}
}
func SetClientQPS(name string, qps float32) {
clientQPS.With(prometheus.Labels{"client": name}).Set(float64(qps))
}
func SetClientBurst(name string, burst int) {
clientBurst.With(prometheus.Labels{"client": name}).Set(float64(burst))
}
package prometheus
import (
"context"
"sync"
"time"
promv1 "github.com/prometheus/client_golang/api/prometheus/v1"
"github.com/prometheus/common/model"
)
//
// Prometheus client
//
// MockProm satisfies the promv1.API interface for testing.
// TODO: move this into something shared under /controller, or into /pkg
type MockProm struct {
Res model.Value
QueriesExecuted []string // expose the queries our Mock Prometheus receives, to test query generation
rwLock sync.Mutex
}
// Query performs a query for the given time.
func (m *MockProm) Query(ctx context.Context, query string, ts time.Time, opts ...promv1.Option) (model.Value, promv1.Warnings, error) {
m.rwLock.Lock()
defer m.rwLock.Unlock()
m.QueriesExecuted = append(m.QueriesExecuted, query)
return m.Res, nil, nil
}
// QueryRange performs a query for the given range.
func (m *MockProm) QueryRange(ctx context.Context, query string, r promv1.Range, opts ...promv1.Option) (model.Value, promv1.Warnings, error) {
m.rwLock.Lock()
defer m.rwLock.Unlock()
m.QueriesExecuted = append(m.QueriesExecuted, query)
return m.Res, nil, nil
}
// AlertManagers returns an overview of the current state of the Prometheus alert
// manager discovery.
func (m *MockProm) AlertManagers(ctx context.Context) (promv1.AlertManagersResult, error) {
return promv1.AlertManagersResult{}, nil
}
// Alerts returns a list of all active alerts.
func (m *MockProm) Alerts(ctx context.Context) (promv1.AlertsResult, error) {
return promv1.AlertsResult{}, nil
}
// CleanTombstones removes the deleted data from disk and cleans up the existing
// tombstones.
func (m *MockProm) CleanTombstones(ctx context.Context) error {
return nil
}
// Config returns the current Prometheus configuration.
func (m *MockProm) Config(ctx context.Context) (promv1.ConfigResult, error) {
return promv1.ConfigResult{}, nil
}
// DeleteSeries deletes data for a selection of series in a time range.
func (m *MockProm) DeleteSeries(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) error {
return nil
}
// Flags returns the flag values that Prometheus was launched with.
func (m *MockProm) Flags(ctx context.Context) (promv1.FlagsResult, error) {
return promv1.FlagsResult{}, nil
}
// LabelValues performs a query for the values of the given label, time range and matchers.
func (m *MockProm) LabelValues(ctx context.Context, label string, matches []string, startTime time.Time, endTime time.Time) (model.LabelValues, promv1.Warnings, error) {
return nil, nil, nil
}
// Series finds series by label matchers.
func (m *MockProm) Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, promv1.Warnings, error) {
return nil, nil, nil
}
// Snapshot creates a snapshot of all current data into
// snapshots/<datetime>-<rand> under the TSDB's data directory and returns the
// directory as response.
func (m *MockProm) Snapshot(ctx context.Context, skipHead bool) (promv1.SnapshotResult, error) {
return promv1.SnapshotResult{}, nil
}
// Targets returns an overview of the current state of the Prometheus target
// discovery.
func (m *MockProm) Targets(ctx context.Context) (promv1.TargetsResult, error) {
return promv1.TargetsResult{}, nil
}
// LabelNames returns the unique label names present in the block in sorted order by given time range and matchers.
func (m *MockProm) LabelNames(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]string, promv1.Warnings, error) {
return []string{}, nil, nil
}
// Runtimeinfo returns the runtime info about Prometheus
func (m *MockProm) Runtimeinfo(ctx context.Context) (promv1.RuntimeinfoResult, error) {
return promv1.RuntimeinfoResult{}, nil
}
// Metadata returns the metadata of the specified metric
func (m *MockProm) Metadata(ctx context.Context, metric string, limit string) (map[string][]promv1.Metadata, error) {
return nil, nil
}
// Rules returns a list of alerting and recording rules that are currently loaded.
func (m *MockProm) Rules(ctx context.Context) (promv1.RulesResult, error) {
return promv1.RulesResult{}, nil
}
// TargetsMetadata returns metadata about metrics currently scraped by the target.
func (m *MockProm) TargetsMetadata(ctx context.Context, matchTarget string, metric string, limit string) ([]promv1.MetricMetadata, error) {
return []promv1.MetricMetadata{}, nil
}
// Buildinfo returns various build information properties about the Prometheus server
func (m *MockProm) Buildinfo(ctx context.Context) (promv1.BuildinfoResult, error) {
return promv1.BuildinfoResult{}, nil
}
// QueryExemplars performs a query for exemplars by the given query and time range.
func (m *MockProm) QueryExemplars(ctx context.Context, query string, startTime time.Time, endTime time.Time) ([]promv1.ExemplarQueryResult, error) {
return []promv1.ExemplarQueryResult{}, nil
}
// TSDB returns the cardinality statistics.
func (m *MockProm) TSDB(ctx context.Context) (promv1.TSDBResult, error) {
return promv1.TSDBResult{}, nil
}
// WalReplay returns the current replay status of the wal.
func (m *MockProm) WalReplay(ctx context.Context) (promv1.WalReplayStatus, error) {
return promv1.WalReplayStatus{}, nil
}
package tls
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"math/big"
"time"
)
type (
// CA provides a certificate authority for TLS-enabled installs.
// Issuing certificates concurrently is not supported.
CA struct {
// Cred contains the CA's credentials.
Cred Cred
// Validity configures the NotBefore and NotAfter parameters for certificates
// issued by this CA.
//
// Currently this is used for the CA's validity too, but nothing should
// assume that the CA's validity period is the same as issued certificates'
// validity.
Validity Validity
// nextSerialNumber is the serial number of the next certificate to issue.
// Serial numbers must not be reused.
//
// It is assumed there is only one instance of CA and it is assumed that a
// given CA object isn't requested to issue certificates concurrently.
//
// For now we do not attempt to meet CABForum requirements (e.g. regarding
// randomness).
nextSerialNumber uint64
// firstCrtExpiration is the time when the first expiration of a certificate
// in the trust chain occurs
firstCrtExpiration time.Time
}
// Validity configures the expiry times of issued certificates.
Validity struct {
// Validity is the duration for which issued certificates are valid. This
// is approximately cert.NotAfter - cert.NotBefore with some additional
// allowance for clock skew.
//
// Currently this is used for the CA's validity too, but nothing should
// assume that the CA's validity period is the same as issued certificates'
// validity.
Lifetime time.Duration
// ClockSkewAllowance is the maximum supported clock skew. Everything that
// processes the certificates must have a system clock that is off by no
// more than this allowance in either direction.
ClockSkewAllowance time.Duration
// ValidFrom is the point in time from which the certificate is valid.
// This is cert.NotBefore with some clock skew allowance.
ValidFrom *time.Time
}
// Issuer implementors signs certificate requests.
Issuer interface {
IssueEndEntityCrt(*x509.CertificateRequest) (Crt, error)
}
)
const (
// DefaultLifetime configures certificate validity.
//
// Initially all certificates will be valid for one year.
//
// TODO: Shorten the validity duration of CA and end-entity certificates downward.
DefaultLifetime = (24 * 365) * time.Hour
// DefaultClockSkewAllowance indicates the maximum allowed difference in clocks
// in the network.
//
// TODO: make it tunable.
//
// TODO: Reconsider how this interacts with the similar logic in the webpki
// verifier; since both are trying to account for clock skew, there is
// somewhat of an over-correction.
DefaultClockSkewAllowance = 10 * time.Second
)
// Finds the time at which the first certificate
// from the chain will expire
func findFirstExpiration(cred *Cred) time.Time {
firstExpiration := cred.Certificate.NotAfter
for _, c := range cred.TrustChain {
if c.NotAfter.Before(firstExpiration) {
firstExpiration = c.NotAfter
}
}
return firstExpiration
}
// NewCA initializes a new CA with default settings.
func NewCA(cred Cred, validity Validity) *CA {
return &CA{cred, validity, uint64(1), findFirstExpiration(&cred)}
}
func init() {
// Assert that the struct implements the interface.
var _ Issuer = &CA{}
}
// CreateRootCA configures a new root CA with the given settings
func CreateRootCA(
name string,
key *ecdsa.PrivateKey,
validity Validity,
) (*CA, error) {
// Configure the root certificate.
t := createTemplate(1, &key.PublicKey, validity)
t.Subject = pkix.Name{CommonName: name}
t.IsCA = true
t.MaxPathLen = -1
t.BasicConstraintsValid = true
t.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageCRLSign
// Self-sign the root certificate.
crtb, err := x509.CreateCertificate(rand.Reader, t, t, key.Public(), key)
if err != nil {
return nil, err
}
c, err := x509.ParseCertificate(crtb)
if err != nil {
return nil, err
}
// The Crt has an empty TrustChain because it's at the root.
cred := validCredOrPanic(key, Crt{Certificate: c})
ca := NewCA(cred, validity)
ca.nextSerialNumber++ // Because we've already created the root cert.
return ca, nil
}
// GenerateKey creates a new P-256 ECDSA private key from the default random
// source.
func GenerateKey() (*ecdsa.PrivateKey, error) {
return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
}
// GenerateRootCAWithDefaults generates a new root CA with default settings.
func GenerateRootCAWithDefaults(name string) (*CA, error) {
// Generate a new root key.
key, err := GenerateKey()
if err != nil {
return nil, err
}
return CreateRootCA(name, key, Validity{})
}
// GenerateCA generates a new intermediate CA.
func (ca *CA) GenerateCA(name string, maxPathLen int) (*CA, error) {
key, err := GenerateKey()
if err != nil {
return nil, err
}
t := ca.createTemplate(&key.PublicKey)
t.Subject = pkix.Name{CommonName: name}
t.IsCA = true
t.MaxPathLen = maxPathLen
t.MaxPathLenZero = true // 0-values are actually 0
t.BasicConstraintsValid = true
t.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageCRLSign
crt, err := ca.Cred.SignCrt(t)
if err != nil {
return nil, err
}
return NewCA(validCredOrPanic(key, crt), ca.Validity), nil
}
// GenerateEndEntityCred creates a new certificate that is valid for the
// given DNS name, generating a new keypair for it.
func (ca *CA) GenerateEndEntityCred(dnsName string) (*Cred, error) {
key, err := GenerateKey()
if err != nil {
return nil, err
}
csr := x509.CertificateRequest{
Subject: pkix.Name{CommonName: dnsName},
DNSNames: []string{dnsName},
PublicKey: &key.PublicKey,
}
crt, err := ca.IssueEndEntityCrt(&csr)
if err != nil {
return nil, err
}
c := validCredOrPanic(key, crt)
return &c, nil
}
// IssueEndEntityCrt creates a new certificate that is valid for the
// given DNS name, generating a new keypair for it.
func (ca *CA) IssueEndEntityCrt(csr *x509.CertificateRequest) (Crt, error) {
pubkey, ok := csr.PublicKey.(*ecdsa.PublicKey)
if !ok {
return Crt{}, fmt.Errorf("CSR must contain an ECDSA public key: %+v", csr.PublicKey)
}
t := ca.createTemplate(pubkey)
t.Issuer = ca.Cred.Crt.Certificate.Subject
t.Subject = csr.Subject
t.Extensions = csr.Extensions
t.ExtraExtensions = csr.ExtraExtensions
t.DNSNames = csr.DNSNames
t.EmailAddresses = csr.EmailAddresses
t.IPAddresses = csr.IPAddresses
t.URIs = csr.URIs
return ca.Cred.SignCrt(t)
}
// createTemplate returns a certificate t for a non-CA certificate with
// no subject name, no subjectAltNames. The t can then be modified into
// a (root) CA t or an end-entity t by the caller.
func (ca *CA) createTemplate(pubkey *ecdsa.PublicKey) *x509.Certificate {
c := createTemplate(ca.nextSerialNumber, pubkey, ca.Validity)
ca.nextSerialNumber++
// if our trust chain contains a certificate that expires
// sooner than the one we intend to issue, we clamp the
// NotAfter time of our newly issued certificate. That ensures
// the proxy will request a new cert before any of the
// certs in the chain are expired.
if ca.firstCrtExpiration.Before(c.NotAfter) {
c.NotAfter = ca.firstCrtExpiration
}
return c
}
// createTemplate returns a certificate t for a non-CA certificate with
// no subject name, no subjectAltNames. The t can then be modified into
// a (root) CA t or an end-entity t by the caller.
func createTemplate(
serialNumber uint64,
k *ecdsa.PublicKey,
v Validity,
) *x509.Certificate {
// ECDSA is used instead of RSA because ECDSA key generation is
// straightforward and fast whereas RSA key generation is extremely slow
// and error-prone.
//
// CA certificates are signed with the same algorithm as end-entity
// certificates because they are relatively short-lived, because using one
// algorithm minimizes exposure to implementation flaws, and to speed up
// signature verification time.
//
// SHA-256 is used because any larger digest would be truncated to 256 bits
// anyway since a P-256 scalar is only 256 bits long.
const SignatureAlgorithm = x509.ECDSAWithSHA256
if v.ValidFrom == nil {
now := time.Now()
v.ValidFrom = &now
}
notBefore, notAfter := v.Window(*v.ValidFrom)
return &x509.Certificate{
SerialNumber: big.NewInt(int64(serialNumber)),
SignatureAlgorithm: SignatureAlgorithm,
NotBefore: notBefore,
NotAfter: notAfter,
PublicKey: k,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageServerAuth,
x509.ExtKeyUsageClientAuth,
},
}
}
// Window returns the time window for which a certificate should be valid.
func (v *Validity) Window(t time.Time) (time.Time, time.Time) {
life := v.Lifetime
if life == 0 {
life = DefaultLifetime
}
skew := v.ClockSkewAllowance
if skew == 0 {
skew = DefaultClockSkewAllowance
}
start := t.Add(-skew)
end := t.Add(life).Add(skew)
return start, end
}
package tls
import (
"bytes"
"crypto/ecdsa"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"reflect"
)
// === ENCODE ===
// EncodeCertificatesPEM encodes the collection of provided certificates as
// a text blob of PEM-encoded certificates.
func EncodeCertificatesPEM(crts ...*x509.Certificate) string {
buf := bytes.Buffer{}
for _, c := range crts {
encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: c.Raw})
}
return buf.String()
}
// EncodePrivateKeyPEM encodes the provided key as PEM-encoded text
func EncodePrivateKeyPEM(k *ecdsa.PrivateKey) ([]byte, error) {
der, err := x509.MarshalECPrivateKey(k)
if err != nil {
return nil, err
}
return pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: der}), nil
}
// EncodePrivateKeyP8 encodes the provided key as PEM-encoded text
func EncodePrivateKeyP8(k *ecdsa.PrivateKey) []byte {
p8, err := x509.MarshalPKCS8PrivateKey(k)
if err != nil {
panic("ECDSA keys must be encodeable as PKCS8")
}
return p8
}
func encode(buf *bytes.Buffer, blk *pem.Block) {
if err := pem.Encode(buf, blk); err != nil {
panic("encoding to memory must not fail")
}
}
// === DECODE ===
// DecodePEMKey parses a PEM-encoded private key from the named path.
func DecodePEMKey(txt string) (GenericPrivateKey, error) {
block, _ := pem.Decode([]byte(txt))
if block == nil {
return nil, errors.New("not PEM-encoded")
}
switch block.Type {
case "EC PRIVATE KEY":
k, err := x509.ParseECPrivateKey(block.Bytes)
if err != nil {
return nil, err
}
return privateKeyEC{k}, nil
case "RSA PRIVATE KEY":
k, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
return privateKeyRSA{k}, nil
case "PRIVATE KEY":
k, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
if ec, ok := k.(*ecdsa.PrivateKey); ok {
return privateKeyEC{ec}, nil
}
if rsa, ok := k.(*rsa.PrivateKey); ok {
return privateKeyRSA{rsa}, nil
}
return nil, fmt.Errorf(
"unsupported PKCS#8 encoded private key type: '%s', linkerd2 only supports ECDSA and RSA private keys",
reflect.TypeOf(k))
default:
return nil, fmt.Errorf("unsupported block type: '%s'", block.Type)
}
}
// DecodePEMCertificates parses a string containing PEM-encoded certificates.
func DecodePEMCertificates(txt string) (certs []*x509.Certificate, err error) {
buf := []byte(txt)
for len(buf) > 0 {
var c *x509.Certificate
c, buf, err = decodeCertificatePEM(buf)
if err != nil {
return
}
if c == nil {
continue // not a CERTIFICATE, skip
}
certs = append(certs, c)
}
return
}
// CertificatesToPool converts a slice of certificates into a cert pool
func CertificatesToPool(certs []*x509.Certificate) *x509.CertPool {
pool := x509.NewCertPool()
for _, c := range certs {
pool.AddCert(c)
}
return pool
}
// DecodePEMCertPool parses a string containing PE-encoded certificates into a CertPool.
func DecodePEMCertPool(txt string) (*x509.CertPool, error) {
certs, err := DecodePEMCertificates(txt)
if err != nil {
return nil, err
}
if len(certs) == 0 {
return nil, errors.New("no certificates found")
}
return CertificatesToPool(certs), nil
}
func decodeCertificatePEM(crtb []byte) (*x509.Certificate, []byte, error) {
block, crtb := pem.Decode(crtb)
if block == nil {
return nil, crtb, errors.New("not a PEM certificate")
}
if block.Type != "CERTIFICATE" {
return nil, nil, nil
}
c, err := x509.ParseCertificate(block.Bytes)
return c, crtb, err
}
package tls
import (
"bytes"
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"os"
"path/filepath"
"time"
)
type (
// PrivateKeyEC wraps an EC private key
privateKeyEC struct {
*ecdsa.PrivateKey
}
// PrivateKeyRSA wraps an RSA private key
privateKeyRSA struct {
*rsa.PrivateKey
}
// GenericPrivateKey represents either an EC or an RSA private key
GenericPrivateKey interface {
matchesCertificate(*x509.Certificate) bool
marshal() ([]byte, error)
}
// Cred is a container for a certificate, trust chain, and private key.
Cred struct {
PrivateKey GenericPrivateKey
Crt
}
// Crt is a container for a certificate and trust chain.
//
// The trust chain stores all issuer certificates from the root at the head to
// the direct issuer at the tail.
Crt struct {
Certificate *x509.Certificate
TrustChain []*x509.Certificate
}
)
func (k privateKeyEC) matchesCertificate(c *x509.Certificate) bool {
pub, ok := c.PublicKey.(*ecdsa.PublicKey)
return ok && pub.X.Cmp(k.X) == 0 && pub.Y.Cmp(k.Y) == 0
}
func (k privateKeyEC) marshal() ([]byte, error) {
return x509.MarshalECPrivateKey(k.PrivateKey)
}
func (k privateKeyRSA) matchesCertificate(c *x509.Certificate) bool {
pub, ok := c.PublicKey.(*rsa.PublicKey)
return ok && pub.N.Cmp(k.N) == 0 && pub.E == k.E
}
func (k privateKeyRSA) marshal() ([]byte, error) {
return x509.MarshalPKCS1PrivateKey(k.PrivateKey), nil
}
// validCredOrPanic creates a Cred, panicking if the key does not match the certificate.
func validCredOrPanic(ecKey *ecdsa.PrivateKey, crt Crt) Cred {
k := privateKeyEC{ecKey}
if !k.matchesCertificate(crt.Certificate) {
panic("Cert's public key does not match private key")
}
return Cred{Crt: crt, PrivateKey: k}
}
// CertPool returns a CertPool containing this Crt.
func (crt *Crt) CertPool() *x509.CertPool {
p := x509.NewCertPool()
p.AddCert(crt.Certificate)
for _, c := range crt.TrustChain {
p.AddCert(c)
}
return p
}
// Verify the validity of the provided certificate
func (crt *Crt) Verify(roots *x509.CertPool, name string, currentTime time.Time) error {
i := x509.NewCertPool()
for _, c := range crt.TrustChain {
i.AddCert(c)
}
vo := x509.VerifyOptions{Roots: roots, Intermediates: i, DNSName: name, CurrentTime: currentTime}
_, err := crt.Certificate.Verify(vo)
if currentTime.IsZero() {
currentTime = time.Now()
}
if crtExpiryError(err) {
return fmt.Errorf("%w - Current Time : %s - Invalid before %s - Invalid After %s", err, currentTime, crt.Certificate.NotBefore, crt.Certificate.NotAfter)
}
return err
}
// ExtractRaw extracts the DER-encoded certificates in the Crt from leaf to root.
func (crt *Crt) ExtractRaw() [][]byte {
chain := make([][]byte, len(crt.TrustChain)+1)
chain[0] = crt.Certificate.Raw
for i, c := range crt.TrustChain {
chain[len(crt.TrustChain)-i] = c.Raw
}
return chain
}
// EncodePEM emits a certificate and trust chain as a
// series of PEM-encoded certificates from leaf to root.
func (crt *Crt) EncodePEM() string {
buf := bytes.Buffer{}
encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: crt.Certificate.Raw})
// Serialize certificates from leaf to root.
n := len(crt.TrustChain)
for i := n - 1; i >= 0; i-- {
encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: crt.TrustChain[i].Raw})
}
return buf.String()
}
// EncodeCertificatePEM emits the Crt's leaf certificate as PEM-encoded text.
func (crt *Crt) EncodeCertificatePEM() string {
return EncodeCertificatesPEM(crt.Certificate)
}
// EncodePrivateKeyPEM emits the private key as PEM-encoded text.
func (cred *Cred) EncodePrivateKeyPEM() string {
b, err := cred.PrivateKey.marshal()
if err != nil {
panic(fmt.Sprintf("Invalid private key: %s", err))
}
return string(pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: b}))
}
// EncodePrivateKeyP8 encodes the provided key to the PKCS#8 binary form.
func (cred *Cred) EncodePrivateKeyP8() ([]byte, error) {
return x509.MarshalPKCS8PrivateKey(cred.PrivateKey)
}
// SignCrt uses this Cred to sign a new certificate.
//
// This may fail if the Cred contains an end-entity certificate.
func (cred *Cred) SignCrt(template *x509.Certificate) (Crt, error) {
crtb, err := x509.CreateCertificate(
rand.Reader,
template,
cred.Crt.Certificate,
template.PublicKey,
cred.PrivateKey,
)
if err != nil {
return Crt{}, err
}
c, err := x509.ParseCertificate(crtb)
if err != nil {
return Crt{}, err
}
crt := Crt{
Certificate: c,
TrustChain: append(cred.Crt.TrustChain, cred.Crt.Certificate),
}
return crt, nil
}
// ValidateAndCreateCreds reads PEM-encoded credentials from strings and validates them
func ValidateAndCreateCreds(crt, key string) (*Cred, error) {
k, err := DecodePEMKey(key)
if err != nil {
return nil, err
}
c, err := DecodePEMCrt(crt)
if err != nil {
return nil, err
}
if !k.matchesCertificate(c.Certificate) {
return nil, errors.New("tls: Public and private key do not match")
}
return &Cred{PrivateKey: k, Crt: *c}, nil
}
// ReadPEMCreds reads PEM-encoded credentials from the named files.
func ReadPEMCreds(keyPath, crtPath string) (*Cred, error) {
keyb, err := os.ReadFile(filepath.Clean(keyPath))
if err != nil {
return nil, err
}
crtb, err := os.ReadFile(filepath.Clean(crtPath))
if err != nil {
return nil, err
}
return ValidateAndCreateCreds(string(crtb), string(keyb))
}
// DecodePEMCrt decodes PEM-encoded certificates from leaf to root.
func DecodePEMCrt(txt string) (*Crt, error) {
certs, err := DecodePEMCertificates(txt)
if err != nil {
return nil, err
}
if len(certs) == 0 {
return nil, errors.New("No certificates found")
}
crt := Crt{
Certificate: certs[0],
TrustChain: make([]*x509.Certificate, len(certs)-1),
}
// The chain is read from Leaf to Root, but we store it from Root to Leaf.
certs = certs[1:]
for i, c := range certs {
crt.TrustChain[len(certs)-i-1] = c
}
return &crt, nil
}
func crtExpiryError(err error) bool {
var cie x509.CertificateInvalidError
if errors.As(err, &cie) {
return cie.Reason == x509.Expired
}
return false
}
package tls
import (
"context"
"crypto/tls"
"fmt"
"path/filepath"
"sync/atomic"
"github.com/fsnotify/fsnotify"
log "github.com/sirupsen/logrus"
)
const dataDirectoryLnName = "..data"
// FsCredsWatcher is used to monitor tls credentials on the filesystem
type FsCredsWatcher struct {
certRootPath string
certFilePath string
keyFilePath string
EventChan chan<- struct{}
ErrorChan chan<- error
}
// NewFsCredsWatcher constructs a FsCredsWatcher instance
func NewFsCredsWatcher(certRootPath string, updateEvent chan<- struct{}, errEvent chan<- error) *FsCredsWatcher {
return &FsCredsWatcher{certRootPath, "", "", updateEvent, errEvent}
}
// WithFilePaths completes the FsCredsWatcher instance with the cert and key files locations
func (fscw *FsCredsWatcher) WithFilePaths(certFilePath, keyFilePath string) *FsCredsWatcher {
fscw.certFilePath = certFilePath
fscw.keyFilePath = keyFilePath
return fscw
}
// StartWatching starts watching the filesystem for cert updates
func (fscw *FsCredsWatcher) StartWatching(ctx context.Context) error {
watcher, err := fsnotify.NewWatcher()
if err != nil {
return err
}
defer watcher.Close()
// no point of proceeding if we fail to watch this
if err := watcher.Add(fscw.certRootPath); err != nil {
return err
}
LOOP:
for {
select {
case event := <-watcher.Events:
log.Debugf("Received event: %v", event)
// Watching the folder for create events as this indicates
// that the secret has been updated.
if event.Op&fsnotify.Create == fsnotify.Create &&
event.Name == filepath.Join(fscw.certRootPath, dataDirectoryLnName) {
fscw.EventChan <- struct{}{}
}
case err := <-watcher.Errors:
fscw.ErrorChan <- err
log.Warnf("Error while watching %s: %s", fscw.certRootPath, err)
break LOOP
case <-ctx.Done():
if err := ctx.Err(); err != nil {
fscw.ErrorChan <- err
}
break LOOP
}
}
return nil
}
// UpdateCert reads the cert and key files and stores the key pair in certVal
func (fscw *FsCredsWatcher) UpdateCert(certVal *atomic.Value) error {
creds, err := ReadPEMCreds(fscw.keyFilePath, fscw.certFilePath)
if err != nil {
return fmt.Errorf("failed to read cert from disk: %w", err)
}
certPEM := creds.EncodePEM()
keyPEM := creds.EncodePrivateKeyPEM()
cert, err := tls.X509KeyPair([]byte(certPEM), []byte(keyPEM))
if err != nil {
return err
}
certVal.Store(&cert)
return nil
}
// ProcessEvents reads from the update and error channels and reloads the certs when necessary
func (fscw *FsCredsWatcher) ProcessEvents(
log *log.Entry,
certVal *atomic.Value,
updateEvent <-chan struct{},
errEvent <-chan error,
) {
for {
select {
case <-updateEvent:
if err := fscw.UpdateCert(certVal); err != nil {
log.Warnf("Skipping update as cert could not be read from disk: %s", err)
} else {
log.Infof("Updated certificate")
}
case err := <-errEvent:
log.Warnf("Received error from fs watcher: %s", err)
}
}
}
package util
import (
"fmt"
"io"
"strings"
httpPb "github.com/linkerd/linkerd2-proxy-api/go/http_types"
)
// KB = Kilobyte
const KB = 1024
// MB = Megabyte
const MB = KB * 1024
// ParseScheme converts a scheme string to protobuf
// TODO: validate scheme
func ParseScheme(scheme string) *httpPb.Scheme {
value, ok := httpPb.Scheme_Registered_value[strings.ToUpper(scheme)]
if ok {
return &httpPb.Scheme{
Type: &httpPb.Scheme_Registered_{
Registered: httpPb.Scheme_Registered(value),
},
}
}
return &httpPb.Scheme{
Type: &httpPb.Scheme_Unregistered{
Unregistered: strings.ToUpper(scheme),
},
}
}
// ParseMethod converts a method string to protobuf
// TODO: validate method
func ParseMethod(method string) *httpPb.HttpMethod {
value, ok := httpPb.HttpMethod_Registered_value[strings.ToUpper(method)]
if ok {
return &httpPb.HttpMethod{
Type: &httpPb.HttpMethod_Registered_{
Registered: httpPb.HttpMethod_Registered(value),
},
}
}
return &httpPb.HttpMethod{
Type: &httpPb.HttpMethod_Unregistered{
Unregistered: strings.ToUpper(method),
},
}
}
// ReadAllLimit reads from r until EOF or until limit bytes are read. If EOF is
// reached, the full bytes are returned. If the limit is reached, an error is
// returned.
func ReadAllLimit(r io.Reader, limit int) ([]byte, error) {
bytes, err := io.ReadAll(io.LimitReader(r, int64(limit)))
if err != nil {
return nil, err
}
if len(bytes) == limit {
return nil, fmt.Errorf("limit reached while reading: %d", limit)
}
return bytes, nil
}
package util
import (
"strings"
log "github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"
)
// ParsePorts parses the given ports string into a map of ports;
// this includes converting port ranges into separate ports
func ParsePorts(portsString string) map[uint32]struct{} {
opaquePorts := make(map[uint32]struct{})
if portsString != "" {
portRanges := GetPortRanges(portsString)
for _, pr := range portRanges {
portsRange, err := ParsePortRange(pr)
if err != nil {
log.Warnf("Invalid port range [%v]: %s", pr, err)
continue
}
for i := portsRange.LowerBound; i <= portsRange.UpperBound; i++ {
opaquePorts[uint32(i)] = struct{}{}
}
}
}
return opaquePorts
}
// ParseContainerOpaquePorts parses the opaque ports annotation into a list of
// port ranges, including validating port ranges and converting named ports
// into their port number equivalents.
func ParseContainerOpaquePorts(override string, namedPorts map[string]int32) []PortRange {
portRanges := GetPortRanges(override)
var values []PortRange
for _, pr := range portRanges {
port, named := namedPorts[pr]
if named {
values = append(values, PortRange{UpperBound: int(port), LowerBound: int(port)})
} else {
pr, err := ParsePortRange(pr)
if err != nil {
log.Warnf("Invalid port range [%v]: %s", pr, err)
continue
}
values = append(values, pr)
}
}
return values
}
func GetNamedPorts(containers []corev1.Container) map[string]int32 {
namedPorts := make(map[string]int32)
for _, container := range containers {
for _, p := range container.Ports {
if p.Name != "" {
namedPorts[p.Name] = p.ContainerPort
}
}
}
return namedPorts
}
// GetPortRanges gets port ranges from an override annotation
func GetPortRanges(override string) []string {
var ports []string
for _, port := range strings.Split(strings.TrimSuffix(override, ","), ",") {
ports = append(ports, strings.TrimSpace(port))
}
return ports
}
// ContainsString checks if a string collections contains the given string.
func ContainsString(str string, collection []string) bool {
for _, e := range collection {
if str == e {
return true
}
}
return false
}
package util
import (
"fmt"
"strconv"
"strings"
)
// PortRange defines the upper- and lower-bounds for a range of ports.
type PortRange struct {
LowerBound int
UpperBound int
}
// ParsePort parses and verifies the validity of the port candidate.
func ParsePort(port string) (int, error) {
i, err := strconv.Atoi(port)
if err != nil || !isPort(i) {
return -1, fmt.Errorf("\"%s\" is not a valid TCP port", port)
}
return i, nil
}
// ParsePortRange parses and checks the provided range candidate to ensure it is
// a valid TCP port range.
func ParsePortRange(portRange string) (PortRange, error) {
bounds := strings.Split(portRange, "-")
if len(bounds) > 2 {
return PortRange{}, fmt.Errorf("ranges expected as <lower>-<upper>")
}
if len(bounds) == 1 {
// If only provided a single value, treat as both lower- and upper-bounds
bounds = append(bounds, bounds[0])
}
lower, err := strconv.Atoi(bounds[0])
if err != nil || !isPort(lower) {
return PortRange{}, fmt.Errorf("\"%s\" is not a valid lower-bound", bounds[0])
}
upper, err := strconv.Atoi(bounds[1])
if err != nil || !isPort(upper) {
return PortRange{}, fmt.Errorf("\"%s\" is not a valid upper-bound", bounds[1])
}
if upper < lower {
return PortRange{}, fmt.Errorf("\"%s\": upper-bound must be greater than or equal to lower-bound", portRange)
}
return PortRange{LowerBound: lower, UpperBound: upper}, nil
}
// isPort checks the provided to determine whether or not the port
// candidate is a valid TCP port number. Valid TCP ports range from 0 to 65535.
func isPort(port int) bool {
return 0 <= port && port <= 65535
}
// Ports returns an array of all the ports contained by this range.
func (pr PortRange) Ports() []uint16 {
var ports []uint16
for i := pr.LowerBound; i <= pr.UpperBound; i++ {
ports = append(ports, uint16(i))
}
return ports
}
func (pr PortRange) ToString() string {
if pr.LowerBound == pr.UpperBound {
return strconv.Itoa(pr.LowerBound)
}
return fmt.Sprintf("%d-%d", pr.LowerBound, pr.UpperBound)
}
package version
import (
"context"
"encoding/json"
"errors"
"fmt"
"net"
"net/http"
"github.com/linkerd/linkerd2/pkg/util"
)
// Channels provides an interface to interact with a set of release channels.
// This module is also responsible for online retrieval of the latest release
// versions.
type Channels struct {
array []channelVersion
}
var (
// CheckURL provides an online endpoint for Linkerd's version checks
CheckURL = "https://versioncheck.linkerd.io/version.json"
)
// NewChannels is used primarily for testing, it returns a Channels struct that
// mimic a GetLatestVersions response.
func NewChannels(channel string) (Channels, error) {
cv, err := parseChannelVersion(channel)
if err != nil {
return Channels{}, err
}
return Channels{
array: []channelVersion{cv},
}, nil
}
// Match validates whether the given version string:
// 1) is a well-formed channel-version string, for example: "edge-19.1.2"
// 2) references a known channel
// 3) matches the version in the known channel
func (c Channels) Match(actualVersion string) error {
if actualVersion == "" {
return errors.New("actual version is empty")
}
if c.Empty() {
return errors.New("unable to determine version channel")
}
actual, err := parseChannelVersion(actualVersion)
if err != nil {
return fmt.Errorf("failed to parse actual version: %w", err)
}
for _, cv := range c.array {
if cv.updateChannel() == actual.updateChannel() {
return match(cv.String(), actualVersion)
}
}
return fmt.Errorf("unsupported version channel: %s", actualVersion)
}
// Determines whether there are any release channels stored in the struct.
func (c Channels) Empty() bool {
return len(c.array) == 0
}
// GetLatestVersions performs an online request to check for the latest Linkerd
// release channels.
func GetLatestVersions(ctx context.Context, uuid string, source string) (Channels, error) {
url := fmt.Sprintf("%s?version=%s&uuid=%s&source=%s", CheckURL, Version, uuid, source)
return getLatestVersions(ctx, http.DefaultClient, url)
}
func getLatestVersions(ctx context.Context, client *http.Client, url string) (Channels, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return Channels{}, err
}
rsp, err := client.Do(req.WithContext(ctx))
if err != nil {
var dnsError *net.DNSError
if errors.As(err, &dnsError) {
return Channels{}, fmt.Errorf("failed to resolve version check server: %s", url)
}
return Channels{}, err
}
defer rsp.Body.Close()
if rsp.StatusCode != 200 {
return Channels{}, fmt.Errorf("unexpected versioncheck response: %s", rsp.Status)
}
bytes, err := util.ReadAllLimit(rsp.Body, util.MB)
if err != nil {
return Channels{}, err
}
var versionRsp map[string]string
err = json.Unmarshal(bytes, &versionRsp)
if err != nil {
return Channels{}, err
}
channels := Channels{}
for c, v := range versionRsp {
cv, err := parseChannelVersion(v)
if err != nil {
return Channels{}, fmt.Errorf("unexpected versioncheck response: %w", err)
}
if c != cv.updateChannel() {
return Channels{}, fmt.Errorf("unexpected versioncheck response: channel in %s does not match %s", cv, c)
}
channels.array = append(channels.array, cv)
}
return channels, nil
}
package version
import (
"fmt"
"strconv"
"strings"
)
// channelVersion is a low-level struct for handling release channels in a
// structured way. It has no dependencies on the rest of the version package.
type channelVersion struct {
channel string
version string
hotpatch *int64
original string
}
// hotpatchSuffix is the suffix applied to channel names to indicate that the
// version string includes a hotpatch number (e.g. dev-0.1.2-3)
const hotpatchSuffix = "Hotpatch"
func (cv channelVersion) String() string {
return cv.original
}
// updateChannel returns the channel name to check for updates. if there's no
// hotpatch number set, then it returns the channel name itself. otherwise it
// returns the channel name suffixed with "Hotpatch" to indicate that a separate
// update channel should be used.
func (cv channelVersion) updateChannel() string {
if cv.hotpatch != nil {
return cv.channel + hotpatchSuffix
}
return cv.channel
}
// versionWithHotpatch returns the version string, suffixed with the hotpatch
// number if it exists.
func (cv channelVersion) versionWithHotpatch() string {
if cv.hotpatch == nil {
return cv.version
}
return fmt.Sprintf("%s-%d", cv.version, *cv.hotpatch)
}
func (cv channelVersion) hotpatchEqual(other channelVersion) bool {
if cv.hotpatch == nil && other.hotpatch == nil {
return true
}
if cv.hotpatch == nil || other.hotpatch == nil {
return false
}
return *cv.hotpatch == *other.hotpatch
}
// parseChannelVersion parses a build string into a channelVersion struct. it
// expects the channel and version to be separated by a hyphen (e.g. dev-0.1.2).
// the version may additionally include a hotpatch number, which is separated
// from the base version by another hyphen (e.g. dev-0.1.2-3). if the version is
// suffixed with any other non-numeric build info strings (e.g. dev-0.1.2-foo),
// those strings are ignored.
func parseChannelVersion(cv string) (channelVersion, error) {
parts := strings.Split(cv, "-")
if len(parts) < 2 {
return channelVersion{}, fmt.Errorf("unsupported version format: %s", cv)
}
channel := parts[0]
version := parts[1]
var hotpatch *int64
for _, part := range parts[2:] {
if i, err := strconv.ParseInt(part, 10, 64); err == nil {
hotpatch = &i
break
}
}
return channelVersion{channel, version, hotpatch, cv}, nil
}
// IsReleaseChannel returns true if the channel of the version is "edge" or
// "stable".
func IsReleaseChannel(version string) (bool, error) {
cv, err := parseChannelVersion(version)
if err != nil {
return false, err
}
return cv.channel == "edge" || cv.channel == "stable", nil
}
package version
import (
"errors"
"fmt"
"os"
)
// Version is updated automatically as part of the build process, and is the
// ground source of truth for the current process's build version.
//
// DO NOT EDIT
var Version = undefinedVersion
// ProxyInitVersion is the pinned version of the proxy-init, from
// https://github.com/linkerd/linkerd2-proxy-init This has to be kept in sync
// with the default version in the control plane's values.yaml.
var ProxyInitVersion = "v2.4.1"
var LinkerdCNIVersion = "v1.5.1"
const (
// undefinedVersion should take the form `channel-version` to conform to
// channelVersion functions.
undefinedVersion = "dev-undefined"
)
func init() {
// Use `$LINKERD_CONTAINER_VERSION_OVERRIDE` as the version only if the
// version wasn't set at link time to minimize the chance of using it
// unintentionally. This mechanism allows the version to be bound at
// container build time instead of at executable link time to improve
// incremental rebuild efficiency.
if Version == undefinedVersion {
override := os.Getenv("LINKERD_CONTAINER_VERSION_OVERRIDE")
if override != "" {
Version = override
}
}
}
// match compares two versions and returns success if they match, or an error
// with a contextual message if they do not.
func match(expectedVersion, actualVersion string) error {
if expectedVersion == "" {
return errors.New("expected version is empty")
} else if actualVersion == "" {
return errors.New("actual version is empty")
}
actual, err := parseChannelVersion(actualVersion)
if err != nil {
return fmt.Errorf("failed to parse actual version: %w", err)
}
expected, err := parseChannelVersion(expectedVersion)
if err != nil {
return fmt.Errorf("failed to parse expected version: %w", err)
}
if actual.channel != expected.channel {
return fmt.Errorf("mismatched channels: running %s but retrieved %s",
actual, expected)
}
if actual.version != expected.version || !actual.hotpatchEqual(expected) {
return fmt.Errorf("is running version %s but the latest %s version is %s",
actual.versionWithHotpatch(), actual.channel, expected.versionWithHotpatch())
}
return nil
}
package fuzzing
import (
fuzz "github.com/AdaLogics/go-fuzz-headers"
"github.com/linkerd/linkerd2/pkg/healthcheck"
"github.com/linkerd/linkerd2/pkg/util"
corev1 "k8s.io/api/core/v1"
)
// FuzzParsePorts fuzzes the ParsePorts function.
func FuzzParsePorts(data []byte) int {
_ = util.ParsePorts(string(data))
return 1
}
// FuzzParseContainerOpaquePorts fuzzes the ParseContainerOpaquePorts function.
func FuzzParseContainerOpaquePorts(data []byte) int {
f := fuzz.NewConsumer(data)
qtyOfContainers, err := f.GetInt()
if err != nil {
return 0
}
qtyOfContainers %= 20
containers := make([]corev1.Container, 0)
for i := 0; i < qtyOfContainers; i++ {
newContainer := corev1.Container{}
err = f.GenerateStruct(&newContainer)
if err != nil {
return 0
}
containers = append(containers, newContainer)
}
override, err := f.GetString()
if err != nil {
return 0
}
_ = util.ParseContainerOpaquePorts(override, util.GetNamedPorts(containers))
return 1
}
// FuzzHealthCheck fuzzes the HealthCheck method for the healthchecker.
func FuzzHealthCheck(data []byte) int {
f := fuzz.NewConsumer(data)
options := &healthcheck.Options{}
err := f.GenerateStruct(options)
if err != nil {
return 0
}
_ = healthcheck.NewHealthChecker([]healthcheck.CategoryID{healthcheck.KubernetesAPIChecks}, options)
return 1
}
package testutil
import (
"fmt"
"os"
"runtime"
"strings"
"testing"
)
const (
envFlag = "GH_ANNOTATION"
rootPath = "/linkerd2/"
)
type level int
const (
err level = iota
warn
)
func (l level) String() string {
switch l {
case err:
return "error"
case warn:
return "warning"
}
panic(fmt.Sprintf("invalid level: %d", l))
}
func echoAnnotation(t *testing.T, l level, args ...interface{}) {
if _, ok := os.LookupEnv(envFlag); ok {
_, fileName, fileLine, ok := runtime.Caller(3)
if !ok {
panic("Couldn't recover runtime info")
}
fileName = fileName[strings.LastIndex(fileName, rootPath)+len(rootPath):]
// In case of coming from `t.Run(testName, ...)`, only take the first part
// of the name; the following parts might not be as generic
parts := strings.Split(t.Name(), "/")
testName := parts[0]
for _, arg := range args {
msg := fmt.Sprintf("%s - %s", testName, arg)
fmt.Printf("::%s file=%s,line=%d::%s\n", l, fileName, fileLine, msg)
}
}
}
func echoAnnotationErr(t *testing.T, args ...interface{}) {
echoAnnotation(t, err, args...)
}
func echoAnnotationWarn(t *testing.T, args ...interface{}) {
echoAnnotation(t, warn, args...)
}
// Error is a wrapper around t.Error()
// args are passed to t.Error(args) and each arg will be sent to stdout formatted
// as a GitHub annotation when the envFlag environment variable is set
func Error(t *testing.T, args ...interface{}) {
t.Helper()
echoAnnotationErr(t, args...)
t.Error(args...)
}
// AnnotatedError is similar to Error() but it also admits a msg string that
// will be used as the GitHub annotation
func AnnotatedError(t *testing.T, msg string, args ...interface{}) {
t.Helper()
echoAnnotationErr(t, msg)
t.Error(args...)
}
// Errorf is a wrapper around t.Errorf()
// format and args are passed to t.Errorf(format, args) and the formatted
// message will be sent to stdout as a GitHub annotation when the envFlag
// environment variable is set
func Errorf(t *testing.T, format string, args ...interface{}) {
t.Helper()
echoAnnotationErr(t, fmt.Sprintf(format, args...))
t.Errorf(format, args...)
}
// AnnotatedErrorf is similar to Errorf() but it also admits a msg string that
// will be used as the GitHub annotation
func AnnotatedErrorf(t *testing.T, msg, format string, args ...interface{}) {
t.Helper()
echoAnnotationErr(t, msg)
t.Errorf(format, args...)
}
// Fatal is a wrapper around t.Fatal()
// args are passed to t.Fatal(args) and each arg will be sent to stdout formatted
// as a GitHub annotation when the envFlag environment variable is set
func Fatal(t *testing.T, args ...interface{}) {
t.Helper()
echoAnnotationErr(t, args)
t.Fatal(args...)
}
// AnnotatedFatal is similar to Fatal() but it also admits a msg string that
// will be used as the GitHub annotation
func AnnotatedFatal(t *testing.T, msg string, args ...interface{}) {
t.Helper()
echoAnnotationErr(t, msg)
t.Fatal(args...)
}
// Fatalf is a wrapper around t.Errorf()
// format and args are passed to t.Fatalf(format, args) and the formatted
// message will be sent to stdout as a GitHub annotation when the envFlag
// environment variable is set
func Fatalf(t *testing.T, format string, args ...interface{}) {
t.Helper()
echoAnnotationErr(t, fmt.Sprintf(format, args...))
t.Fatalf(format, args...)
}
// AnnotatedFatalf is similar to Fatalf() but it also admits a msg string that
// will be used as the GitHub annotation
func AnnotatedFatalf(t *testing.T, msg, format string, args ...interface{}) {
t.Helper()
echoAnnotationErr(t, msg)
t.Fatalf(format, args...)
}
// AnnotatedWarn is a wrapper around t.Log() but it also admits a msg string that
// will be used as the GitHub warning annotation
func AnnotatedWarn(t *testing.T, msg string, args ...interface{}) {
t.Helper()
echoAnnotationWarn(t, msg)
t.Log(args...)
}
package testutil
import (
"fmt"
"strings"
jsonpatch "github.com/evanphx/json-patch"
"github.com/linkerd/linkerd2/pkg/k8s"
v1 "k8s.io/api/core/v1"
"sigs.k8s.io/yaml"
)
func applyPatch(in string, patchJSON []byte) (string, error) {
patch, err := jsonpatch.DecodePatch(patchJSON)
if err != nil {
return "", err
}
json, err := yaml.YAMLToJSON([]byte(in))
if err != nil {
return "", err
}
patched, err := patch.Apply(json)
if err != nil {
return "", err
}
return string(patched), nil
}
// PatchDeploy patches a manifest by applying annotations
func PatchDeploy(in string, name string, annotations map[string]string) (string, error) {
ops := []string{
fmt.Sprintf(`{"op": "replace", "path": "/metadata/name", "value": "%s"}`, name),
fmt.Sprintf(`{"op": "replace", "path": "/spec/selector/matchLabels/app", "value": "%s"}`, name),
fmt.Sprintf(`{"op": "replace", "path": "/spec/template/metadata/labels/app", "value": "%s"}`, name),
}
if len(annotations) > 0 {
ops = append(ops, `{"op": "add", "path": "/spec/template/metadata/annotations", "value": {}}`)
for k, v := range annotations {
ops = append(ops,
fmt.Sprintf(`{"op": "add", "path": "/spec/template/metadata/annotations/%s", "value": "%s"}`, strings.ReplaceAll(k, "/", "~1"), v),
)
}
}
patchJSON := []byte(fmt.Sprintf("[%s]", strings.Join(ops, ",")))
return applyPatch(in, patchJSON)
}
// GetProxyContainer get the proxy containers
func GetProxyContainer(containers []v1.Container) *v1.Container {
for _, c := range containers {
container := c
if container.Name == k8s.ProxyContainerName {
return &container
}
}
return nil
}
package testutil
import (
"fmt"
"strconv"
"strings"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"github.com/linkerd/linkerd2/pkg/k8s"
)
const enabled = "true"
// InjectValidator is used as a helper to generate
// correct injector flags and annotations and verify
// injected pods
type InjectValidator struct {
NoInitContainer bool
AutoInject bool
AdminPort int
ControlPort int
EnableDebug bool
EnableExternalProfiles bool
ImagePullPolicy string
InboundPort int
InitImage string
InitImageVersion string
OutboundPort int
CPULimit string
EphemeralStorageLimit string
CPURequest string
MemoryLimit string
MemoryRequest string
EphemeralStorageRequest string
Image string
LogLevel string
LogFormat string
UID int
GID int
Version string
RequireIdentityOnPorts string
SkipOutboundPorts string
OpaquePorts string
SkipInboundPorts string
OutboundConnectTimeout string
InboundConnectTimeout string
WaitBeforeExitSeconds int
SkipSubnets string
ShutdownGracePeriod string
}
func (iv *InjectValidator) getContainer(pod *v1.PodSpec, name string, isInit bool) *v1.Container {
containers := pod.Containers
if isInit {
containers = pod.InitContainers
}
for _, container := range containers {
if container.Name == name {
return &container
}
}
return nil
}
func (iv *InjectValidator) validateEnvVar(container *v1.Container, envName, expectedValue string) error {
for _, env := range container.Env {
if env.Name == envName {
if env.Value == expectedValue {
return nil
}
return fmt.Errorf("env: %s, expected: %s, actual %s", envName, expectedValue, env.Value)
}
}
return fmt.Errorf("cannot find env: %s", envName)
}
func (iv *InjectValidator) validatePort(container *v1.Container, portName string, expectedValue int) error {
for _, port := range container.Ports {
if port.Name == portName {
if port.ContainerPort == int32(expectedValue) {
return nil
}
return fmt.Errorf("port: %s, expected: %d, actual %d", portName, expectedValue, port.ContainerPort)
}
}
return fmt.Errorf("cannot find port: %s", portName)
}
func (iv *InjectValidator) validateDebugContainer(pod *v1.PodSpec) error {
if iv.EnableDebug {
proxyContainer := iv.getContainer(pod, k8s.DebugContainerName, false)
if proxyContainer == nil {
return fmt.Errorf("container %s missing", k8s.DebugContainerName)
}
}
return nil
}
func (iv *InjectValidator) validateProxyContainer(pod *v1.PodSpec) error {
proxyContainer := iv.getContainer(pod, k8s.ProxyContainerName, false)
if proxyContainer == nil {
return fmt.Errorf("container %s missing", k8s.ProxyContainerName)
}
if iv.AdminPort != 0 {
if err := iv.validateEnvVar(proxyContainer, "LINKERD2_PROXY_ADMIN_LISTEN_ADDR", fmt.Sprintf("[::]:%d", iv.AdminPort)); err != nil {
return err
}
if proxyContainer.LivenessProbe.HTTPGet.Port.IntVal != int32(iv.AdminPort) {
return fmt.Errorf("livenessProbe: expected: %d, actual %d", iv.AdminPort, proxyContainer.LivenessProbe.HTTPGet.Port.IntVal)
}
if proxyContainer.ReadinessProbe.HTTPGet.Port.IntVal != int32(iv.AdminPort) {
return fmt.Errorf("readinessProbe: expected: %d, actual %d", iv.AdminPort, proxyContainer.LivenessProbe.HTTPGet.Port.IntVal)
}
if err := iv.validatePort(proxyContainer, k8s.ProxyAdminPortName, iv.AdminPort); err != nil {
return err
}
}
if iv.ControlPort != 0 {
if err := iv.validateEnvVar(proxyContainer, "LINKERD2_PROXY_CONTROL_LISTEN_ADDR", fmt.Sprintf("[::]:%d", iv.ControlPort)); err != nil {
return err
}
}
if iv.EnableExternalProfiles {
if err := iv.validateEnvVar(proxyContainer, "LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES", "."); err != nil {
return err
}
}
if iv.ImagePullPolicy != "" {
if string(proxyContainer.ImagePullPolicy) != iv.ImagePullPolicy {
return fmt.Errorf("pullPolicy: expected: %s, actual %s", iv.ImagePullPolicy, string(proxyContainer.ImagePullPolicy))
}
}
if iv.InboundPort != 0 {
if err := iv.validateEnvVar(proxyContainer, "LINKERD2_PROXY_INBOUND_LISTEN_ADDR", fmt.Sprintf("[::]:%d", iv.InboundPort)); err != nil {
return err
}
if proxyContainer.LivenessProbe.HTTPGet.Port.IntVal != int32(iv.AdminPort) {
return fmt.Errorf("livenessProbe: expected: %d, actual %d", iv.AdminPort, proxyContainer.LivenessProbe.HTTPGet.Port.IntVal)
}
if proxyContainer.ReadinessProbe.HTTPGet.Port.IntVal != int32(iv.AdminPort) {
return fmt.Errorf("readinessProbe: expected: %d, actual %d", iv.AdminPort, proxyContainer.LivenessProbe.HTTPGet.Port.IntVal)
}
if err := iv.validatePort(proxyContainer, k8s.ProxyPortName, iv.InboundPort); err != nil {
return err
}
}
if iv.OutboundPort != 0 {
if err := iv.validateEnvVar(
proxyContainer,
"LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS",
fmt.Sprintf("127.0.0.1:%d", iv.OutboundPort),
); err != nil {
return err
}
}
if iv.CPULimit != "" {
limit := resource.MustParse(iv.CPULimit)
if proxyContainer.Resources.Limits.Cpu() != nil {
if !proxyContainer.Resources.Limits.Cpu().Equal(limit) {
return fmt.Errorf("CpuLimit: expected %v, actual %v", &limit, proxyContainer.Resources.Limits.Cpu())
}
} else {
return fmt.Errorf("CpuLimit: expected %v, but none", &limit)
}
}
if iv.CPURequest != "" {
request := resource.MustParse(iv.CPURequest)
if proxyContainer.Resources.Requests.Cpu() != nil {
if !proxyContainer.Resources.Requests.Cpu().Equal(request) {
return fmt.Errorf("CpuRequest: expected %v, actual %v", &request, proxyContainer.Resources.Requests.Cpu())
}
} else {
return fmt.Errorf("CpuRequest: expected %v, but none", &request)
}
}
if iv.MemoryLimit != "" {
limit := resource.MustParse(iv.MemoryLimit)
if proxyContainer.Resources.Limits.Memory() != nil {
if !proxyContainer.Resources.Limits.Memory().Equal(limit) {
return fmt.Errorf("MemLimit: expected %v, actual %v", &limit, proxyContainer.Resources.Limits.Memory())
}
} else {
return fmt.Errorf("MemLimit: expected %v, but none", &limit)
}
}
if iv.MemoryRequest != "" {
request := resource.MustParse(iv.MemoryRequest)
if proxyContainer.Resources.Requests.Memory() != nil {
if !proxyContainer.Resources.Requests.Memory().Equal(request) {
return fmt.Errorf("MemRequest: expected %v, actual %v", &request, proxyContainer.Resources.Requests.Memory())
}
} else {
return fmt.Errorf("MemRequest: expected %v, but none", &request)
}
}
if iv.EphemeralStorageLimit != "" {
limit := resource.MustParse(iv.EphemeralStorageLimit)
if proxyContainer.Resources.Limits.StorageEphemeral() != nil {
if !proxyContainer.Resources.Limits.StorageEphemeral().Equal(limit) {
return fmt.Errorf("EphemeralStorageLimit: expected %v, actual %v", &limit, proxyContainer.Resources.Limits.StorageEphemeral())
}
} else {
return fmt.Errorf("EphemeralStorageLimit: expected %v, but none", &limit)
}
}
if iv.EphemeralStorageRequest != "" {
request := resource.MustParse(iv.EphemeralStorageRequest)
if proxyContainer.Resources.Requests.StorageEphemeral() != nil {
if !proxyContainer.Resources.Requests.StorageEphemeral().Equal(request) {
return fmt.Errorf("EphemeralStorageRequest: expected %v, actual %v", &request, proxyContainer.Resources.Requests.StorageEphemeral())
}
} else {
return fmt.Errorf("EphemeralStorageRequest: expected %v, but none", &request)
}
}
if iv.Image != "" || iv.Version != "" {
image := strings.Split(proxyContainer.Image, ":")
if len(image) != 2 {
return fmt.Errorf("invalid proxy container image string: %s", proxyContainer.Image)
}
if iv.Image != "" {
if image[0] != iv.Image {
return fmt.Errorf("proxyImage: expected %s, actual %s", iv.Image, image[0])
}
}
if iv.Version != "" {
if image[1] != iv.Version {
return fmt.Errorf("proxyImageVersion: expected %s, actual %s", iv.Version, image[1])
}
}
}
if iv.LogLevel != "" {
expectedLogLevel := fmt.Sprintf("%s,linkerd_proxy_http::client[{headers}]=off", iv.LogLevel)
if err := iv.validateEnvVar(proxyContainer, "LINKERD2_PROXY_LOG", expectedLogLevel); err != nil {
return err
}
}
if iv.LogFormat != "" {
if err := iv.validateEnvVar(proxyContainer, "LINKERD2_PROXY_LOG_FORMAT", iv.LogFormat); err != nil {
return err
}
}
if iv.UID != 0 {
if proxyContainer.SecurityContext.RunAsUser == nil {
return fmt.Errorf("no RunAsUser specified")
}
if *proxyContainer.SecurityContext.RunAsUser != int64(iv.UID) {
return fmt.Errorf("runAsUser: expected %d, actual %d", iv.UID, *proxyContainer.SecurityContext.RunAsUser)
}
}
if iv.GID != 0 {
if proxyContainer.SecurityContext.RunAsGroup == nil {
return fmt.Errorf("no RunAsGroup specified")
}
if *proxyContainer.SecurityContext.RunAsGroup != int64(iv.GID) {
return fmt.Errorf("runAsGroup: expected %d, actual %d", iv.GID, *proxyContainer.SecurityContext.RunAsGroup)
}
}
if iv.RequireIdentityOnPorts != "" {
if err := iv.validateEnvVar(proxyContainer, "LINKERD2_PROXY_INBOUND_PORTS_REQUIRE_IDENTITY", iv.RequireIdentityOnPorts); err != nil {
return err
}
}
if iv.OpaquePorts != "" {
if err := iv.validateEnvVar(proxyContainer, "LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION", iv.OpaquePorts); err != nil {
return err
}
}
if iv.OutboundConnectTimeout != "" {
if err := iv.validateEnvVar(proxyContainer, "LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT", iv.OutboundConnectTimeout); err != nil {
return err
}
}
if iv.OutboundConnectTimeout != "" {
if err := iv.validateEnvVar(proxyContainer, "LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT", iv.InboundConnectTimeout); err != nil {
return err
}
}
if iv.WaitBeforeExitSeconds != 0 {
expectedCmd := fmt.Sprintf("/bin/sleep,%d", iv.WaitBeforeExitSeconds)
actual := strings.Join(proxyContainer.Lifecycle.PreStop.Exec.Command, ",")
if expectedCmd != strings.Join(proxyContainer.Lifecycle.PreStop.Exec.Command, ",") {
return fmt.Errorf("preStopHook: expected %s, actual %s", expectedCmd, actual)
}
}
if iv.ShutdownGracePeriod != "" {
if err := iv.validateEnvVar(proxyContainer, "LINKERD2_PROXY_SHUTDOWN_GRACE_PERIOD", iv.ShutdownGracePeriod); err != nil {
return err
}
}
return nil
}
func (iv *InjectValidator) validateInitContainer(pod *v1.PodSpec) error {
if iv.NoInitContainer {
return nil
}
initContainer := iv.getContainer(pod, k8s.InitContainerName, true)
if initContainer == nil {
return fmt.Errorf("container %s missing", k8s.InitContainerName)
}
if iv.InitImage != "" || iv.InitImageVersion != "" {
image := strings.Split(initContainer.Image, ":")
if len(image) != 2 {
return fmt.Errorf("invalid proxy init image string: %s", initContainer.Image)
}
if iv.InitImage != "" {
if image[0] != iv.InitImage {
return fmt.Errorf("proxyInitImage: expected %s, actual %s", iv.InitImage, image[0])
}
}
if iv.InitImageVersion != "" {
if image[1] != iv.InitImageVersion {
return fmt.Errorf("proxyInitImageVersion: expected %s, actual %s", iv.InitImageVersion, image[1])
}
}
}
if iv.InboundPort != 0 {
if err := iv.validateArg(initContainer, "--incoming-proxy-port", strconv.Itoa(iv.InboundPort)); err != nil {
return err
}
}
if iv.OutboundPort != 0 {
if err := iv.validateArg(initContainer, "--proxy-uid", strconv.Itoa(iv.UID)); err != nil {
return err
}
}
if iv.UID != 0 {
if err := iv.validateArg(initContainer, "--outgoing-proxy-port", strconv.Itoa(iv.OutboundPort)); err != nil {
return err
}
}
if iv.SkipInboundPorts != "" {
expectedPorts := fmt.Sprintf("%d,%d,%s", iv.ControlPort, iv.AdminPort, iv.SkipInboundPorts)
if err := iv.validateArg(initContainer, "--inbound-ports-to-ignore", expectedPorts); err != nil {
return err
}
}
if iv.SkipOutboundPorts != "" {
if err := iv.validateArg(initContainer, "--outbound-ports-to-ignore", iv.SkipOutboundPorts); err != nil {
return err
}
}
if iv.SkipSubnets != "" {
if err := iv.validateArg(initContainer, "--skip-subnets", iv.SkipSubnets); err != nil {
return err
}
}
return nil
}
func (iv *InjectValidator) validateArg(container *v1.Container, argName, expectedValue string) error {
for i, arg := range container.Args {
if arg == argName {
if len(container.Args) < i+2 {
return fmt.Errorf("No value for arg %s", argName)
}
if container.Args[i+1] != expectedValue {
return fmt.Errorf("container arg %s expected: %s, actual %s", argName, expectedValue, container.Args[i+1])
}
return nil
}
}
return fmt.Errorf("Could not find arg: %s", argName)
}
// ValidatePod validates that the pod had been configured
// according by the injector correctly
func (iv *InjectValidator) ValidatePod(pod *v1.PodSpec) error {
if err := iv.validateProxyContainer(pod); err != nil {
return err
}
if err := iv.validateInitContainer(pod); err != nil {
return err
}
if err := iv.validateDebugContainer(pod); err != nil {
return err
}
return nil
}
// GetFlagsAndAnnotations retrieves the injector config flags and annotations
// based on the options provided
func (iv *InjectValidator) GetFlagsAndAnnotations() ([]string, map[string]string) {
annotations := make(map[string]string)
var flags []string
if iv.AutoInject {
annotations[k8s.ProxyInjectAnnotation] = k8s.ProxyInjectEnabled
}
if iv.AdminPort != 0 {
annotations[k8s.ProxyAdminPortAnnotation] = strconv.Itoa(iv.AdminPort)
flags = append(flags, fmt.Sprintf("--admin-port=%s", strconv.Itoa(iv.AdminPort)))
}
if iv.ControlPort != 0 {
annotations[k8s.ProxyControlPortAnnotation] = strconv.Itoa(iv.ControlPort)
flags = append(flags, fmt.Sprintf("--control-port=%s", strconv.Itoa(iv.ControlPort)))
}
if iv.EnableDebug {
annotations[k8s.ProxyEnableDebugAnnotation] = enabled
flags = append(flags, "--enable-debug-sidecar")
}
if iv.EnableExternalProfiles {
annotations[k8s.ProxyEnableExternalProfilesAnnotation] = enabled
flags = append(flags, "--enable-external-profiles")
}
if iv.ImagePullPolicy != "" {
annotations[k8s.ProxyImagePullPolicyAnnotation] = iv.ImagePullPolicy
flags = append(flags, fmt.Sprintf("--image-pull-policy=%s", iv.ImagePullPolicy))
}
if iv.InboundPort != 0 {
annotations[k8s.ProxyInboundPortAnnotation] = strconv.Itoa(iv.InboundPort)
flags = append(flags, fmt.Sprintf("--inbound-port=%s", strconv.Itoa(iv.InboundPort)))
}
if iv.InitImage != "" {
annotations[k8s.ProxyInitImageAnnotation] = iv.InitImage
flags = append(flags, fmt.Sprintf("--init-image=%s", iv.InitImage))
}
if iv.InitImageVersion != "" {
annotations[k8s.ProxyInitImageVersionAnnotation] = iv.InitImageVersion
flags = append(flags, fmt.Sprintf("--init-image-version=%s", iv.InitImageVersion))
}
if iv.OutboundPort != 0 {
annotations[k8s.ProxyOutboundPortAnnotation] = strconv.Itoa(iv.OutboundPort)
flags = append(flags, fmt.Sprintf("--outbound-port=%s", strconv.Itoa(iv.OutboundPort)))
}
if iv.CPULimit != "" {
annotations[k8s.ProxyCPULimitAnnotation] = iv.CPULimit
flags = append(flags, fmt.Sprintf("--proxy-cpu-limit=%s", iv.CPULimit))
}
if iv.CPURequest != "" {
annotations[k8s.ProxyCPURequestAnnotation] = iv.CPURequest
flags = append(flags, fmt.Sprintf("--proxy-cpu-request=%s", iv.CPURequest))
}
if iv.MemoryLimit != "" {
annotations[k8s.ProxyMemoryLimitAnnotation] = iv.MemoryLimit
flags = append(flags, fmt.Sprintf("--proxy-memory-limit=%s", iv.MemoryLimit))
}
if iv.EphemeralStorageLimit != "" {
annotations[k8s.ProxyEphemeralStorageLimitAnnotation] = iv.EphemeralStorageLimit
// TODO - `inject` does not support the `--set` flag, so we can't add this
// flags = append(flags, "--set", fmt.Sprintf("proxy.resources.ephemeral-storage.limit=%s", iv.EphemeralStorageLimit))
}
if iv.MemoryRequest != "" {
annotations[k8s.ProxyMemoryRequestAnnotation] = iv.MemoryRequest
flags = append(flags, fmt.Sprintf("--proxy-memory-request=%s", iv.MemoryRequest))
}
if iv.EphemeralStorageRequest != "" {
annotations[k8s.ProxyEphemeralStorageRequestAnnotation] = iv.EphemeralStorageRequest
// TODO - `inject` does not support the `--set` flag, so we can't add this
// flags = append(flags, "--set", fmt.Sprintf("proxy.resources.ephemeral-storage.request=%s", iv.EphemeralStorageRequest))
}
if iv.Image != "" {
annotations[k8s.ProxyImageAnnotation] = iv.Image
flags = append(flags, fmt.Sprintf("--proxy-image=%s", iv.Image))
}
if iv.LogLevel != "" {
annotations[k8s.ProxyLogLevelAnnotation] = iv.LogLevel
flags = append(flags, fmt.Sprintf("--proxy-log-level=%s", iv.LogLevel))
}
if iv.LogFormat != "" {
annotations[k8s.ProxyLogFormatAnnotation] = iv.LogFormat
}
if iv.UID != 0 {
annotations[k8s.ProxyUIDAnnotation] = strconv.Itoa(iv.UID)
flags = append(flags, fmt.Sprintf("--proxy-uid=%s", strconv.Itoa(iv.UID)))
}
if iv.GID != 0 {
annotations[k8s.ProxyGIDAnnotation] = strconv.Itoa(iv.GID)
flags = append(flags, fmt.Sprintf("--proxy-gid=%s", strconv.Itoa(iv.GID)))
}
if iv.Version != "" {
annotations[k8s.ProxyVersionOverrideAnnotation] = iv.Version
flags = append(flags, fmt.Sprintf("--proxy-version=%s", iv.Version))
}
if iv.RequireIdentityOnPorts != "" {
annotations[k8s.ProxyRequireIdentityOnInboundPortsAnnotation] = iv.RequireIdentityOnPorts
flags = append(flags, fmt.Sprintf("--require-identity-on-inbound-ports =%s", iv.RequireIdentityOnPorts))
}
if iv.SkipInboundPorts != "" {
annotations[k8s.ProxyIgnoreInboundPortsAnnotation] = iv.SkipInboundPorts
flags = append(flags, fmt.Sprintf("--skip-inbound-ports=%s", iv.SkipInboundPorts))
}
if iv.OpaquePorts != "" {
annotations[k8s.ProxyOpaquePortsAnnotation] = iv.OpaquePorts
}
if iv.SkipOutboundPorts != "" {
annotations[k8s.ProxyIgnoreOutboundPortsAnnotation] = iv.SkipOutboundPorts
flags = append(flags, fmt.Sprintf("--skip-outbound-ports=%s", iv.SkipOutboundPorts))
}
if iv.OutboundConnectTimeout != "" {
annotations[k8s.ProxyOutboundConnectTimeout] = iv.OutboundConnectTimeout
}
if iv.InboundConnectTimeout != "" {
annotations[k8s.ProxyInboundConnectTimeout] = iv.InboundConnectTimeout
}
if iv.WaitBeforeExitSeconds != 0 {
annotations[k8s.ProxyWaitBeforeExitSecondsAnnotation] = strconv.Itoa(iv.WaitBeforeExitSeconds)
flags = append(flags, fmt.Sprintf("--wait-before-exit-secondst=%s", strconv.Itoa(iv.WaitBeforeExitSeconds)))
}
if iv.SkipSubnets != "" {
annotations[k8s.ProxySkipSubnetsAnnotation] = iv.SkipSubnets
flags = append(flags, fmt.Sprintf("--skip-subnets=%s", iv.SkipSubnets))
}
if iv.ShutdownGracePeriod != "" {
annotations[k8s.ProxyShutdownGracePeriodAnnotation] = iv.ShutdownGracePeriod
flags = append(flags, fmt.Sprintf("--shutdown-grace-period=%s", iv.ShutdownGracePeriod))
}
return flags, annotations
}
package testutil
import (
"context"
"fmt"
"testing"
)
// TestResourcesPostInstall tests resources post control plane installation
func TestResourcesPostInstall(namespace string, services []Service, deploys map[string]DeploySpec, h *TestHelper, t *testing.T) {
ctx := context.Background()
// Tests Namespace
err := h.CheckIfNamespaceExists(ctx, namespace)
if err != nil {
AnnotatedFatalf(t, "received unexpected output",
"received unexpected output\n%s", err)
}
// Tests Services
for _, svc := range services {
if err := h.CheckService(ctx, svc.Namespace, svc.Name); err != nil {
AnnotatedErrorf(t, fmt.Sprintf("error validating service [%s/%s]", svc.Namespace, svc.Name),
"error validating service [%s/%s]:\n%s", svc.Namespace, svc.Name, err)
}
}
// Tests Pods and Deployments
for deploy, spec := range deploys {
if err := h.CheckPods(ctx, spec.Namespace, deploy, spec.Replicas); err != nil {
//nolint:errorlint
if rce, ok := err.(*RestartCountError); ok {
AnnotatedWarn(t, "CheckPods timed-out", rce)
} else {
AnnotatedFatal(t, "CheckPods timed-out", err)
}
}
}
}
// ExerciseTestAppEndpoint tests if the emojivoto service is reachable
func ExerciseTestAppEndpoint(endpoint, namespace string, h *TestHelper) error {
testAppURL, err := h.URLFor(context.Background(), namespace, "web", 8080)
if err != nil {
return err
}
for i := 0; i < 30; i++ {
_, err := h.HTTPGetURL(testAppURL + endpoint)
if err != nil {
return err
}
}
return nil
}
package testutil
import (
"context"
"errors"
"fmt"
"os"
"os/exec"
"regexp"
"strings"
"testing"
"time"
"github.com/linkerd/linkerd2/pkg/k8s"
corev1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
// Loads the GCP auth plugin
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
)
// KubernetesHelper provides Kubernetes-related test helpers. It connects to the
// Kubernetes API using the environment's configured kubeconfig file.
type KubernetesHelper struct {
k8sContext string
clientset *kubernetes.Clientset
retryFor func(time.Duration, func() error) error
}
// RestartCountError is returned by CheckPods() whenever a pod has restarted exactly one time.
// Consumers should log this type of error instead of failing the test.
// This is to alleviate CI flakiness stemming from a containerd bug.
// See https://github.com/kubernetes/kubernetes/issues/89064
// See https://github.com/containerd/containerd/issues/4068
type RestartCountError struct {
msg string
}
func (e *RestartCountError) Error() string {
return e.msg
}
// NewKubernetesHelper creates a new instance of KubernetesHelper.
func NewKubernetesHelper(k8sContext string, retryFor func(time.Duration, func() error) error) (*KubernetesHelper, error) {
rules := clientcmd.NewDefaultClientConfigLoadingRules()
overrides := &clientcmd.ConfigOverrides{CurrentContext: k8sContext}
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, overrides)
config, err := kubeConfig.ClientConfig()
if err != nil {
return nil, err
}
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, err
}
return &KubernetesHelper{
clientset: clientset,
k8sContext: k8sContext,
retryFor: retryFor,
}, nil
}
// CheckIfNamespaceExists checks if a namespace exists.
func (h *KubernetesHelper) CheckIfNamespaceExists(ctx context.Context, namespace string) error {
_, err := h.clientset.CoreV1().Namespaces().Get(ctx, namespace, metav1.GetOptions{})
return err
}
// SwitchContext will re-build the clientset with the given context. Useful when
// testing multiple clusters at the same time
func (h *KubernetesHelper) SwitchContext(ctx string) error {
rules := clientcmd.NewDefaultClientConfigLoadingRules()
overrides := &clientcmd.ConfigOverrides{CurrentContext: ctx}
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, overrides)
config, err := kubeConfig.ClientConfig()
if err != nil {
return err
}
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
return err
}
h.clientset = clientset
return nil
}
// GetSecret retrieves a Kubernetes Secret
func (h *KubernetesHelper) GetSecret(ctx context.Context, namespace, name string) (*corev1.Secret, error) {
return h.clientset.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{})
}
func (h *KubernetesHelper) createNamespaceIfNotExists(ctx context.Context, namespace string, annotations, labels map[string]string) error {
err := h.CheckIfNamespaceExists(ctx, namespace)
if err != nil {
ns := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Labels: labels,
Annotations: annotations,
Name: namespace,
},
}
_, err = h.clientset.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{})
if err != nil {
return err
}
}
return nil
}
// DeleteNamespaceIfExists attempts to delete the given namespace,
// using the K8s API directly
func (h *KubernetesHelper) DeleteNamespaceIfExists(ctx context.Context, namespace string) error {
err := h.clientset.CoreV1().Namespaces().Delete(ctx, namespace, metav1.DeleteOptions{})
if err != nil && !kerrors.IsNotFound(err) {
return err
}
return nil
}
// CreateControlPlaneNamespaceIfNotExists creates linkerd control plane namespace.
func (h *KubernetesHelper) CreateControlPlaneNamespaceIfNotExists(ctx context.Context, namespace string) error {
return h.createNamespaceIfNotExists(ctx, namespace, nil, nil)
}
// CreateDataPlaneNamespaceIfNotExists creates a dataplane namespace if it does not already exist,
// with a test.linkerd.io/is-test-data-plane label for easier cleanup afterwards
func (h *KubernetesHelper) CreateDataPlaneNamespaceIfNotExists(ctx context.Context, namespace string, annotations map[string]string) error {
return h.createNamespaceIfNotExists(ctx, namespace, annotations, map[string]string{"test.linkerd.io/is-test-data-plane": "true"})
}
// KubectlApply applies a given configuration string in a namespace. If the
// namespace does not exist, it creates it first. If no namespace is provided,
// it does not specify the `--namespace` flag.
func (h *KubernetesHelper) KubectlApply(stdin string, namespace string) (string, error) {
args := []string{"apply", "-f", "-"}
if namespace != "" {
args = append(args, "--namespace", namespace)
}
return h.Kubectl(stdin, args...)
}
// KubectlApplyWithArgs applies a given configuration string with the passed
// flags
func (h *KubernetesHelper) KubectlApplyWithArgs(stdin string, cmdArgs ...string) (string, error) {
args := []string{"apply"}
args = append(args, cmdArgs...)
args = append(args, "-f", "-")
return h.Kubectl(stdin, args...)
}
// Kubectl executes an arbitrary Kubectl command
func (h *KubernetesHelper) Kubectl(stdin string, arg ...string) (string, error) {
withContext := append([]string{"--context=" + h.k8sContext}, arg...)
cmd := exec.Command("kubectl", withContext...)
cmd.Stdin = strings.NewReader(stdin)
out, err := cmd.CombinedOutput()
return string(out), err
}
// KubectlApplyWithContext applies a given configuration with the given flags
func (h *KubernetesHelper) KubectlApplyWithContext(stdin string, context string, arg ...string) (string, error) {
args := append([]string{"apply"}, arg...)
return h.KubectlWithContext(stdin, context, args...)
}
// KubectlWithContext will call the kubectl binary with any optional arguments
// provided and an arbitrary, given context. Useful when working with k8s
// resources in a multi-cluster context. Optionally, stdin can be piped to
// kubectl.
func (h *KubernetesHelper) KubectlWithContext(stdin string, context string, arg ...string) (string, error) {
withContext := append([]string{"--context=" + context}, arg...)
cmd := exec.Command("kubectl", withContext...)
cmd.Stdin = strings.NewReader(stdin)
out, err := cmd.CombinedOutput()
return string(out), err
}
// GetConfigUID returns the uid associated to the linkerd-config ConfigMap resource
// in the given namespace
func (h *KubernetesHelper) GetConfigUID(ctx context.Context, namespace string) (string, error) {
cm, err := h.clientset.CoreV1().ConfigMaps(namespace).Get(ctx, k8s.ConfigConfigMapName, metav1.GetOptions{})
if err != nil {
return "", err
}
return string(cm.GetUID()), nil
}
// GetResources returns the resource limits and requests set on a deployment
// of the set name in the given namespace
func (h *KubernetesHelper) GetResources(ctx context.Context, containerName, deploymentName, namespace string) (corev1.ResourceRequirements, error) {
dep, err := h.clientset.AppsV1().Deployments(namespace).Get(ctx, deploymentName, metav1.GetOptions{})
if err != nil {
return corev1.ResourceRequirements{}, err
}
for _, container := range dep.Spec.Template.Spec.Containers {
if container.Name == containerName {
return container.Resources, nil
}
}
return corev1.ResourceRequirements{}, fmt.Errorf("container %s not found in deployment %s in namespace %s", containerName, deploymentName, namespace)
}
// CheckPods checks that a deployment in a namespace contains the expected
// number of pods in the Running state, and that no pods have been restarted.
func (h *KubernetesHelper) CheckPods(ctx context.Context, namespace string, deploymentName string, replicas int) error {
var checkedPods []corev1.Pod
err := h.retryFor(60*time.Minute, func() error {
checkedPods = []corev1.Pod{}
pods, err := h.GetPodsForDeployment(ctx, namespace, deploymentName)
if err != nil {
return err
}
var deploymentReplicas int
for _, pod := range pods {
checkedPods = append(checkedPods, pod)
deploymentReplicas++
if pod.Status.Phase != "Running" {
return fmt.Errorf("Pod [%s] in namespace [%s] is not running",
pod.Name, pod.Namespace)
}
for _, container := range pod.Status.ContainerStatuses {
if !container.Ready {
return fmt.Errorf("Container [%s] in pod [%s] in namespace [%s] is not running",
container.Name, pod.Name, pod.Namespace)
}
}
}
if deploymentReplicas != replicas {
return fmt.Errorf("Expected there to be [%d] pods in deployment [%s] in namespace [%s], but found [%d]",
replicas, deploymentName, namespace, deploymentReplicas)
}
return nil
})
if err != nil {
return err
}
for _, pod := range checkedPods {
for _, status := range append(pod.Status.ContainerStatuses, pod.Status.InitContainerStatuses...) {
errStr := fmt.Sprintf("Container [%s] in pod [%s] in namespace [%s] has restart count [%d]",
status.Name, pod.Name, pod.Namespace, status.RestartCount)
if status.RestartCount == 1 {
return &RestartCountError{errStr}
}
if status.RestartCount > 1 {
return errors.New(errStr)
}
}
}
return nil
}
// CheckService checks that a service exists in a namespace.
func (h *KubernetesHelper) CheckService(ctx context.Context, namespace string, serviceName string) error {
return h.retryFor(10*time.Second, func() error {
_, err := h.clientset.CoreV1().Services(namespace).Get(ctx, serviceName, metav1.GetOptions{})
return err
})
}
// GetService gets a service that exists in a namespace.
func (h *KubernetesHelper) GetService(ctx context.Context, namespace string, serviceName string) (*corev1.Service, error) {
service, err := h.clientset.CoreV1().Services(namespace).Get(ctx, serviceName, metav1.GetOptions{})
if err != nil {
return nil, err
}
return service, nil
}
// GetEndpoints gets endpoints that exist in a namespace.
func (h *KubernetesHelper) GetEndpoints(ctx context.Context, namespace string, serviceName string) (*corev1.Endpoints, error) {
ep, err := h.clientset.CoreV1().Endpoints(namespace).Get(ctx, serviceName, metav1.GetOptions{})
if err != nil {
return nil, err
}
return ep, nil
}
// GetPods returns all pods with the given labels
func (h *KubernetesHelper) GetPods(ctx context.Context, namespace string, podLabels map[string]string) ([]corev1.Pod, error) {
podList, err := h.clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{
LabelSelector: labels.Set(podLabels).AsSelector().String(),
})
if err != nil {
return nil, err
}
return podList.Items, nil
}
// GetPodsForDeployment returns all pods for the given deployment
func (h *KubernetesHelper) GetPodsForDeployment(ctx context.Context, namespace string, deploymentName string) ([]corev1.Pod, error) {
deploy, err := h.clientset.AppsV1().Deployments(namespace).Get(ctx, deploymentName, metav1.GetOptions{})
if err != nil {
return nil, err
}
return h.GetPods(ctx, namespace, deploy.Spec.Selector.MatchLabels)
}
// GetPodNamesForDeployment returns all pod names for the given deployment
func (h *KubernetesHelper) GetPodNamesForDeployment(ctx context.Context, namespace string, deploymentName string) ([]string, error) {
podList, err := h.GetPodsForDeployment(ctx, namespace, deploymentName)
if err != nil {
return nil, err
}
pods := make([]string, 0)
for _, pod := range podList {
pods = append(pods, pod.Name)
}
return pods, nil
}
// ParseNamespacedResource extracts a namespace and resource name from a string
// that's in the format namespace/resource. If the strings is in a different
// format it returns an error.
func (h *KubernetesHelper) ParseNamespacedResource(resource string) (string, string, error) {
r := regexp.MustCompile(`^(.+)\/(.+)$`)
matches := r.FindAllStringSubmatch(resource, 2)
if len(matches) == 0 {
return "", "", fmt.Errorf("string [%s] didn't contain expected format for namespace/resource, extracted: %v", resource, matches)
}
return matches[0][1], matches[0][2], nil
}
// URLFor creates a kubernetes port-forward, runs it, and returns the URL that
// tests can use for access to the given deployment. Note that the port-forward
// remains running for the duration of the test.
func (h *KubernetesHelper) URLFor(ctx context.Context, namespace, deployName string, remotePort int) (string, error) {
k8sAPI, err := k8s.NewAPI("", h.k8sContext, "", []string{}, 0)
if err != nil {
return "", err
}
pf, err := k8s.NewPortForward(ctx, k8sAPI, namespace, deployName, "localhost", 0, remotePort, false)
if err != nil {
return "", err
}
if err = pf.Init(); err != nil {
return "", err
}
return pf.URLFor(""), nil
}
// WaitRollout blocks until all the given deployments have been completely
// rolled out (and their pods are ready)
func (h *KubernetesHelper) WaitRollout(t *testing.T, deploys map[string]DeploySpec) {
t.Helper()
// Use default context
h.WaitRolloutWithContext(t, deploys, h.k8sContext)
}
// WaitRolloutWithContext blocks until all the given deployments in a provided
// k8s context have been completely rolled out (and their pods are ready)
func (h *KubernetesHelper) WaitRolloutWithContext(t *testing.T, deploys map[string]DeploySpec, context string) {
t.Helper()
for deploy, deploySpec := range deploys {
stat, err := h.KubectlWithContext("", context, "--namespace="+deploySpec.Namespace,
"rollout", "status", "--timeout=5m", "deploy/"+deploy)
if err != nil {
desc, _ := h.KubectlWithContext("", context, "--namespace="+deploySpec.Namespace,
"describe", "po")
AnnotatedFatalf(t,
fmt.Sprintf("failed to wait rollout of deploy/%s", deploy),
"failed to wait for rollout of deploy/%s: %s: %s\n---\n%s", deploy, err, stat, desc)
}
}
}
// WaitUntilDeployReady will block and wait until all given deploys have been
// rolled out and their pods are in a 'ready' status. The difference between
// this and WaitRollout is that WaitUntilDeployReady uses CheckPods underneath,
// instead of relying on the 'rollout' command. WaitUntilDeployReady will also
// retry for a long period time. This function is used to block tests from
// running until the control plane and extensions are ready.
func (h *KubernetesHelper) WaitUntilDeployReady(deploys map[string]DeploySpec) {
ctx := context.Background()
for deploy, spec := range deploys {
if err := h.CheckPods(ctx, spec.Namespace, deploy, 1); err != nil {
var out string
//nolint:errorlint
if rce, ok := err.(*RestartCountError); ok {
out = fmt.Sprintf("Error running test: failed to wait for deploy/%s to become 'ready', too many restarts (%v)\n", deploy, rce)
} else {
out = fmt.Sprintf("Error running test: failed to wait for deploy/%s to become 'ready', timed out waiting for condition\n", deploy)
}
os.Stderr.Write([]byte(out))
os.Exit(1)
}
}
}
package testutil
import (
"bufio"
"fmt"
"io"
"os/exec"
"strings"
"time"
)
// Stream provides the ability of read the output of an executing process while
// it is still running
type Stream struct {
cmd *exec.Cmd
out io.ReadCloser
}
// Stop closes the stream and kills the process
func (s *Stream) Stop() {
s.out.Close()
s.cmd.Process.Kill()
}
// ReadUntil reads from the process output until specified number of lines has
// been reached, or until a timeout
func (s *Stream) ReadUntil(lineCount int, timeout time.Duration) ([]string, error) {
output := make([]string, 0)
lines := make(chan string)
timeoutAfter := time.NewTimer(timeout)
defer timeoutAfter.Stop()
scanner := bufio.NewScanner(s.out)
stopSignal := false
go func() {
for scanner.Scan() {
lines <- scanner.Text()
if stopSignal {
close(lines)
return
}
}
}()
for {
select {
case <-timeoutAfter.C:
stopSignal = true
return output, fmt.Errorf("cmd [%s] Timed out trying to read %d lines", strings.Join(s.cmd.Args, " "), lineCount)
case line := <-lines:
output = append(output, line)
if len(output) >= lineCount {
stopSignal = true
return output, nil
}
}
}
}
package testutil
import (
"fmt"
"strings"
"time"
)
// TapEvent represents a tap event
type TapEvent struct {
Method string
Authority string
Path string
HTTPStatus string
GrpcStatus string
TLS string
LineCount int
}
// Tap executes a tap command and converts the command's streaming output into tap
// events using each line's "id" field
func Tap(target string, h *TestHelper, arg ...string) ([]*TapEvent, error) {
cmd := append([]string{"viz", "tap", target}, arg...)
outputStream, err := h.LinkerdRunStream(cmd...)
if err != nil {
return nil, err
}
defer outputStream.Stop()
outputLines, err := outputStream.ReadUntil(10, 1*time.Minute)
if err != nil {
return nil, err
}
tapEventByID := make(map[string]*TapEvent)
for _, line := range outputLines {
fields := toFieldMap(line)
obj, ok := tapEventByID[fields["id"]]
if !ok {
obj = &TapEvent{}
tapEventByID[fields["id"]] = obj
}
obj.LineCount++
obj.TLS = fields["tls"]
switch fields["type"] {
case "req":
obj.Method = fields[":method"]
obj.Authority = fields[":authority"]
obj.Path = fields[":path"]
case "rsp":
obj.HTTPStatus = fields[":status"]
case "end":
obj.GrpcStatus = fields["grpc-status"]
}
}
output := make([]*TapEvent, 0)
for _, obj := range tapEventByID {
if obj.LineCount == 3 { // filter out incomplete events
output = append(output, obj)
}
}
return output, nil
}
func toFieldMap(line string) map[string]string {
fields := strings.Fields(line)
fieldMap := map[string]string{"type": fields[0]}
for _, field := range fields[1:] {
parts := strings.SplitN(field, "=", 2)
fieldMap[parts[0]] = parts[1]
}
return fieldMap
}
// ValidateExpected compares the received tap event with the expected tap event
func ValidateExpected(events []*TapEvent, expectedEvent TapEvent) error {
if len(events) == 0 {
return fmt.Errorf("Expected tap events, got nothing")
}
for _, event := range events {
if *event != expectedEvent {
return fmt.Errorf("Unexpected tap event [%+v]; expected=[%+v]", *event, expectedEvent)
}
}
return nil
}
package testutil
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"testing"
"text/template"
"github.com/go-test/deep"
"github.com/sergi/go-diff/diffmatchpatch"
"gopkg.in/yaml.v2"
)
// TestDataDiffer holds configuration for generating test diff
type TestDataDiffer struct {
PrettyDiff bool
UpdateFixtures bool
RejectPath string
}
// DiffTestYAML compares a YAML structure to a fixture on the filestystem.
func (td *TestDataDiffer) DiffTestYAML(path string, actualYAML string) error {
expectedYAML := ReadTestdata(path)
return td.diffTestYAML(path, actualYAML, expectedYAML)
}
// DiffTestYAMLTemplate compares a YAML structure to a parameterized fixture on the filestystem.
func (td *TestDataDiffer) DiffTestYAMLTemplate(path string, actualYAML string, params any) error {
file := filepath.Join("testdata", path)
t, err := template.New(path).ParseFiles(file)
if err != nil {
return fmt.Errorf("Failed to read YAML template from %s: %w", path, err)
}
var buf bytes.Buffer
err = t.Execute(&buf, params)
if err != nil {
return fmt.Errorf("Failed to build YAML from template %s: %w", path, err)
}
return td.diffTestYAML(path, actualYAML, buf.String())
}
func (td *TestDataDiffer) diffTestYAML(path, actualYAML, expectedYAML string) error {
actual, err := unmarshalYAML([]byte(actualYAML))
if err != nil {
return fmt.Errorf("Failed to unmarshal generated YAML: %w", err)
}
expected, err := unmarshalYAML([]byte(expectedYAML))
if err != nil {
return fmt.Errorf("Failed to unmarshal expected YAML: %w", err)
}
diff := deep.Equal(expected, actual)
if diff == nil {
return nil
}
td.storeActual(path, []byte(actualYAML))
e := fmt.Sprintf("YAML mismatches %s:", path)
for _, d := range diff {
e += fmt.Sprintf("\n %s", d)
}
return errors.New(e)
}
// DiffTestdata generates the diff for actual w.r.the file in path
func (td *TestDataDiffer) DiffTestdata(t *testing.T, path, actual string) {
t.Helper()
expected := ReadTestdata(path)
if actual == expected {
return
}
dmp := diffmatchpatch.New()
diffs := dmp.DiffMain(expected, actual, true)
diffs = dmp.DiffCleanupSemantic(diffs)
var diff string
if td.PrettyDiff {
diff = dmp.DiffPrettyText(diffs)
} else {
diff = dmp.PatchToText(dmp.PatchMake(diffs))
}
t.Errorf("mismatch: %s\n%s", path, diff)
td.storeActual(path, []byte(actual))
}
func (td *TestDataDiffer) storeActual(path string, actual []byte) {
if td.UpdateFixtures {
writeTestdata(path, actual)
}
if td.RejectPath != "" {
writeRejects(path, actual, td.RejectPath)
}
}
// ReadTestdata reads a file and returns the contents of that file as a string.
func ReadTestdata(fileName string) string {
file, err := os.Open(filepath.Join("testdata", fileName))
if err != nil {
panic(fmt.Sprintf("Failed to open expected output file: %v", err))
}
fixture, err := io.ReadAll(file)
if err != nil {
panic(fmt.Sprintf("Failed to read expected output file: %v", err))
}
return string(fixture)
}
func unmarshalYAML(data []byte) ([]interface{}, error) {
objs := make([]interface{}, 0)
rd := bytes.NewReader(data)
decoder := yaml.NewDecoder(rd)
for {
var obj interface{}
if err := decoder.Decode(&obj); err != nil {
if errors.Is(err, io.EOF) {
return objs, nil
}
return nil, err
}
objs = append(objs, obj)
}
}
func writeTestdata(fileName string, data []byte) {
p := filepath.Join("testdata", fileName)
if err := os.WriteFile(p, data, 0600); err != nil {
panic(err)
}
}
func writeRejects(origFileName string, data []byte, rejectPath string) {
p := filepath.Join(rejectPath, origFileName+".rej")
if err := os.WriteFile(p, data, 0600); err != nil {
panic(err)
}
}
package testutil
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"errors"
"flag"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
log "github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"
)
// TestHelper provides helpers for running the linkerd integration tests.
type TestHelper struct {
linkerd string
version string
namespace string
vizNamespace string
upgradeFromVersion string
clusterDomain string
externalIssuer bool
externalPrometheus bool
multicluster bool
multiclusterSrcCtx string
multiclusterTgtCtx string
uninstall bool
cni bool
calico bool
dualStack bool
nativeSidecar bool
defaultInboundPolicy string
httpClient http.Client
KubernetesHelper
helm
installedExtensions []string
}
type helm struct {
path string
charts string
multiclusterChart string
vizChart string
vizStableChart string
releaseName string
multiclusterReleaseName string
upgradeFromVersion string
}
// DeploySpec is used to hold information about what deploys we should verify during testing
type DeploySpec struct {
Namespace string
Replicas int
}
// Service is used to hold information about a Service we should verify during testing
type Service struct {
Namespace string
Name string
}
// LinkerdDeployReplicasEdge is a map containing the number of replicas for each Deployment and the main
// container name in the current core installation
var LinkerdDeployReplicasEdge = map[string]DeploySpec{
"linkerd-destination": {"linkerd", 1},
"linkerd-identity": {"linkerd", 1},
"linkerd-proxy-injector": {"linkerd", 1},
}
// LinkerdDeployReplicasStable is a map containing the number of replicas for
// each Deployment and the main container name. Override whenever edge deviates
// from stable.
var LinkerdDeployReplicasStable = LinkerdDeployReplicasEdge
// LinkerdVizDeployReplicas is a map containing the number of replicas for
// each Deployment and the main container name in the current linkerd-viz
// installation
var LinkerdVizDeployReplicas = map[string]DeploySpec{
"prometheus": {"linkerd-viz", 1},
"metrics-api": {"linkerd-viz", 1},
"tap": {"linkerd-viz", 1},
"tap-injector": {"linkerd-viz", 1},
"web": {"linkerd-viz", 1},
}
// MulticlusterDeployReplicas is a map containing the number of replicas for
// each Deployment and the main container name for multicluster components
var MulticlusterDeployReplicas = map[string]DeploySpec{
"linkerd-gateway": {"linkerd-multicluster", 1},
}
// MulticlusterSourceReplicas is a map containing the number of replicas for the
// Service Mirror component; component that we'd only expect in the
// source cluster.
var MulticlusterSourceReplicas = map[string]DeploySpec{
"linkerd-service-mirror-target": {Namespace: "linkerd-multicluster", Replicas: 1},
}
// ExternalVizDeployReplicas has an external prometheus instance that's in a
// separate namespace
var ExternalVizDeployReplicas = map[string]DeploySpec{
"prometheus": {"external-prometheus", 1},
"metrics-api": {"linkerd-viz", 1},
"tap": {"linkerd-viz", 1},
"tap-injector": {"linkerd-viz", 1},
"web": {"linkerd-viz", 1},
}
// SourceContextKey represents the key used to get the name of the Kubernetes
// context corresponding to a source cluster in multicluster tests
var SourceContextKey = "source"
// TargetContextKey represents the key used to get the name of the Kubernetes
// context corresponding to a source cluster in multicluster tests
var TargetContextKey = "target"
// NewGenericTestHelper returns a new *TestHelper from the options provided as function parameters.
// This helper was created to be able to reuse this package without hard restrictions
// as seen in `NewTestHelper()` which is primarily used with integration tests
// See - https://github.com/linkerd/linkerd2/issues/4530
func NewGenericTestHelper(
linkerd,
version,
namespace,
vizNamespace,
upgradeFromVersion,
clusterDomain,
helmPath,
helmCharts,
helmReleaseName,
helmMulticlusterReleaseName,
helmMulticlusterChart string,
externalIssuer,
externalPrometheus,
multicluster,
cni,
calico,
uninstall bool,
httpClient http.Client,
kubernetesHelper KubernetesHelper,
) *TestHelper {
return &TestHelper{
linkerd: linkerd,
version: version,
namespace: namespace,
vizNamespace: vizNamespace,
upgradeFromVersion: upgradeFromVersion,
helm: helm{
path: helmPath,
charts: helmCharts,
multiclusterChart: helmMulticlusterChart,
multiclusterReleaseName: helmMulticlusterReleaseName,
releaseName: helmReleaseName,
upgradeFromVersion: upgradeFromVersion,
},
clusterDomain: clusterDomain,
externalIssuer: externalIssuer,
externalPrometheus: externalPrometheus,
uninstall: uninstall,
cni: cni,
calico: calico,
httpClient: httpClient,
multicluster: multicluster,
KubernetesHelper: kubernetesHelper,
}
}
// NewTestHelper creates a new instance of TestHelper for the current test run.
// The new TestHelper can be configured via command line flags.
func NewTestHelper() *TestHelper {
exit := func(code int, msg string) {
fmt.Fprintln(os.Stderr, msg)
os.Exit(code)
}
// TODO (matei): clean-up flags
k8sContext := flag.String("k8s-context", "", "kubernetes context associated with the test cluster")
linkerdExec := flag.String("linkerd", "", "path to the linkerd binary to test")
namespace := flag.String("linkerd-namespace", "linkerd", "the namespace where linkerd is installed")
vizNamespace := flag.String("viz-namespace", "linkerd-viz", "the namespace where linkerd viz extension is installed")
multicluster := flag.Bool("multicluster", false, "when specified the multicluster install functionality is tested")
multiclusterSourceCtx := flag.String("multicluster-source-context", "k3d-source", "the context belonging to source cluster in multicluster test")
multiclusterTargetCtx := flag.String("multicluster-target-context", "k3d-target", "the context belonging to target cluster in multicluster test")
helmPath := flag.String("helm-path", "target/helm", "path of the Helm binary")
helmCharts := flag.String("helm-charts", "charts/linkerd2", "path to linkerd2's Helm charts")
multiclusterHelmChart := flag.String("multicluster-helm-chart", "charts/linkerd-multicluster", "path to linkerd2's multicluster Helm chart")
vizHelmChart := flag.String("viz-helm-chart", "charts/linkerd-viz", "path to linkerd2's viz extension Helm chart")
vizHelmStableChart := flag.String("viz-helm-stable-chart", "charts/linkerd-viz", "path to linkerd2's viz extension stable Helm chart")
helmReleaseName := flag.String("helm-release", "", "install linkerd via Helm using this release name")
multiclusterHelmReleaseName := flag.String("multicluster-helm-release", "", "install linkerd multicluster via Helm using this release name")
upgradeFromVersion := flag.String("upgrade-from-version", "", "when specified, the upgrade test uses it as the base version of the upgrade")
clusterDomain := flag.String("cluster-domain", "cluster.local", "when specified, the install test uses a custom cluster domain")
externalIssuer := flag.Bool("external-issuer", false, "when specified, the install test uses it to install linkerd with --identity-external-issuer=true")
externalPrometheus := flag.Bool("external-prometheus", false, "when specified, the install test uses an external prometheus")
runTests := flag.Bool("integration-tests", false, "must be provided to run the integration tests")
verbose := flag.Bool("verbose", false, "turn on debug logging")
upgradeHelmFromVersion := flag.String("upgrade-helm-from-version", "", "Indicate a version of the Linkerd helm chart from which the helm installation is being upgraded")
uninstall := flag.Bool("uninstall", false, "whether to run the 'linkerd uninstall' integration test")
cni := flag.Bool("cni", false, "whether to install linkerd with CNI enabled")
calico := flag.Bool("calico", false, "whether to install calico CNI plugin")
dualStack := flag.Bool("dual-stack", false, "whether to run the dual-stack tests")
nativeSidecar := flag.Bool("native-sidecar", false, "whether to install using native sidecar injection")
defaultInboundPolicy := flag.String("default-inbound-policy", "", "if non-empty, passed to --set proxy.defaultInboundPolicy at linkerd's install time")
flag.Parse()
if !*runTests {
exit(0, "integration tests not enabled: enable with -integration-tests")
}
if *linkerdExec == "" {
exit(1, "-linkerd flag is required")
}
linkerd, err := filepath.Abs(*linkerdExec)
if err != nil {
exit(1, fmt.Sprintf("abs: %s", err))
}
if *verbose {
log.SetLevel(log.DebugLevel)
} else {
log.SetLevel(log.PanicLevel)
}
testHelper := &TestHelper{
linkerd: linkerd,
namespace: *namespace,
vizNamespace: *vizNamespace,
upgradeFromVersion: *upgradeFromVersion,
multicluster: *multicluster,
multiclusterSrcCtx: *multiclusterSourceCtx,
multiclusterTgtCtx: *multiclusterTargetCtx,
helm: helm{
path: *helmPath,
charts: *helmCharts,
multiclusterChart: *multiclusterHelmChart,
vizChart: *vizHelmChart,
vizStableChart: *vizHelmStableChart,
releaseName: *helmReleaseName,
multiclusterReleaseName: *multiclusterHelmReleaseName,
upgradeFromVersion: *upgradeHelmFromVersion,
},
clusterDomain: *clusterDomain,
externalIssuer: *externalIssuer,
externalPrometheus: *externalPrometheus,
cni: *cni,
calico: *calico,
dualStack: *dualStack,
nativeSidecar: *nativeSidecar,
uninstall: *uninstall,
defaultInboundPolicy: *defaultInboundPolicy,
}
version, err := testHelper.LinkerdRun("version", "--client", "--short")
if err != nil {
exit(1, fmt.Sprintf("error getting linkerd version: %s", err.Error()))
}
testHelper.version = strings.TrimSpace(version)
kubernetesHelper, err := NewKubernetesHelper(*k8sContext, RetryFor)
if err != nil {
exit(1, fmt.Sprintf("error creating kubernetes helper: %s", err.Error()))
}
testHelper.KubernetesHelper = *kubernetesHelper
testHelper.httpClient = http.Client{
Timeout: 10 * time.Second,
}
return testHelper
}
// GetVersion returns the version of linkerd to test. This version corresponds
// to the client version of the linkerd binary provided via the -linkerd command
// line flag.
func (h *TestHelper) GetVersion() string {
return h.version
}
// GetLinkerdNamespace returns the namespace where linkerd is installed. Set the
// namespace using the -linkerd-namespace command line flag.
func (h *TestHelper) GetLinkerdNamespace() string {
return h.namespace
}
// GetVizNamespace returns the namespace where linkerd Viz Extension is installed. Set the
// namespace using the -linkerd-namespace command line flag.
func (h *TestHelper) GetVizNamespace() string {
return h.vizNamespace
}
// GetMulticlusterNamespace returns the namespace where multicluster
// components are installed.
func (h *TestHelper) GetMulticlusterNamespace() string {
return fmt.Sprintf("%s-multicluster", h.GetLinkerdNamespace())
}
// GetMulticlusterContexts returns a map with the context names for the clusters
// used in the test
func (h *TestHelper) GetMulticlusterContexts() map[string]string {
return map[string]string{
"source": h.multiclusterSrcCtx,
"target": h.multiclusterTgtCtx,
}
}
// GetTestNamespace returns the namespace for the given test. The test namespace
// is prefixed with the linkerd namespace.
func (h *TestHelper) GetTestNamespace(testName string) string {
return h.namespace + "-" + testName
}
// GetHelmReleaseName returns the name of the Linkerd installation Helm release
func (h *TestHelper) GetHelmReleaseName() string {
return h.helm.releaseName
}
// GetMulticlusterHelmReleaseName returns the name of the Linkerd multicluster installation Helm release
func (h *TestHelper) GetMulticlusterHelmReleaseName() string {
return h.helm.multiclusterReleaseName
}
// GetHelmCharts returns the path to the Linkerd Helm chart
func (h *TestHelper) GetHelmCharts() string {
return h.helm.charts
}
// GetMulticlusterHelmChart returns the path to the Linkerd multicluster Helm chart
func (h *TestHelper) GetMulticlusterHelmChart() string {
return h.helm.multiclusterChart
}
// GetLinkerdVizHelmChart returns the path to the Linkerd viz Helm chart
func (h *TestHelper) GetLinkerdVizHelmChart() string {
return h.helm.vizChart
}
// GetLinkerdVizHelmStableChart returns the path to the Linkerd viz Helm
// stable chart
func (h *TestHelper) GetLinkerdVizHelmStableChart() string {
return h.helm.vizStableChart
}
// UpgradeHelmFromVersion returns the version from which Linkerd should be upgraded with Helm
func (h *TestHelper) UpgradeHelmFromVersion() string {
return h.helm.upgradeFromVersion
}
// ExternalIssuer determines whether linkerd should be installed with --identity-external-issuer
func (h *TestHelper) ExternalIssuer() bool {
return h.externalIssuer
}
// ExternalPrometheus determines whether linkerd should be installed with --set prometheusUrl
func (h *TestHelper) ExternalPrometheus() bool {
return h.externalPrometheus
}
// Multicluster determines whether multicluster components should be installed
func (h *TestHelper) Multicluster() bool {
return h.multicluster
}
// Uninstall determines whether the "linkerd uninstall" integration test should be run
func (h *TestHelper) Uninstall() bool {
return h.uninstall
}
// DefaultInboundPolicy returns the override value for proxy.defaultInboundPolicy
func (h *TestHelper) DefaultInboundPolicy() string {
return h.defaultInboundPolicy
}
// UpgradeFromVersion returns the base version of the upgrade test.
func (h *TestHelper) UpgradeFromVersion() string {
return h.upgradeFromVersion
}
// GetClusterDomain returns the custom cluster domain that needs to be used during linkerd installation
func (h *TestHelper) GetClusterDomain() string {
return h.clusterDomain
}
// CNI determines whether CNI should be enabled
func (h *TestHelper) CNI() bool {
return h.cni
}
// Calico determines whether Calico CNI plug-in is enabled
func (h *TestHelper) Calico() bool {
return h.calico
}
// DualStack determines whether the DualStack tests are run
func (h *TestHelper) DualStack() bool {
return h.dualStack
}
// NativeSidecar determines whether native sidecar injection is enabled
func (h *TestHelper) NativeSidecar() bool {
return h.nativeSidecar
}
// AddInstalledExtension adds an extension name to installedExtensions to
// track the currently installed linkerd extensions.
func (h *TestHelper) AddInstalledExtension(extensionName string) {
h.installedExtensions = append(h.installedExtensions, extensionName)
}
// GetInstalledExtensions gets a list currently installed extensions
// in a test run.
func (h *TestHelper) GetInstalledExtensions() []string {
return h.installedExtensions
}
// CreateTLSSecret creates a TLS Kubernetes secret
func (h *TestHelper) CreateTLSSecret(name, root, cert, key string) error {
secret := fmt.Sprintf(`
apiVersion: v1
data:
ca.crt: %s
tls.crt: %s
tls.key: %s
kind: Secret
metadata:
name: %s
type: kubernetes.io/tls`, base64.StdEncoding.EncodeToString([]byte(root)), base64.StdEncoding.EncodeToString([]byte(cert)), base64.StdEncoding.EncodeToString([]byte(key)), name)
_, err := h.KubectlApply(secret, h.GetLinkerdNamespace())
return err
}
// CmdRun executes an arbitrary command by calling the binary and return its
// output
func (h *TestHelper) CmdRun(cmd string, arg ...string) (string, error) {
out, stderr, err := combinedOutput("", cmd, arg...)
if err != nil {
return out, fmt.Errorf("command failed: '%s %s'\n%w\n%s", cmd, strings.Join(arg, " "), err, stderr)
}
return out, nil
}
// LinkerdRun executes a linkerd command returning its stdout.
func (h *TestHelper) LinkerdRun(arg ...string) (string, error) {
out, stderr, err := h.PipeToLinkerdRun("", arg...)
if err != nil {
return out, fmt.Errorf("command failed: linkerd %s\n%w\n%s", strings.Join(arg, " "), err, stderr)
}
return out, nil
}
// PipeToLinkerdRun executes a linkerd command appended with the
// --linkerd-namespace flag, and provides a string at Stdin.
func (h *TestHelper) PipeToLinkerdRun(stdin string, arg ...string) (string, string, error) {
withParams := append([]string{"--linkerd-namespace", h.namespace, "--context=" + h.k8sContext}, arg...)
return combinedOutput(stdin, h.linkerd, withParams...)
}
// HelmRun executes a helm command appended with the --context
func (h *TestHelper) HelmRun(arg ...string) (string, string, error) {
return h.PipeToHelmRun("", arg...)
}
// PipeToHelmRun executes a Helm command appended with the
// --context flag, and provides a string at Stdin.
func (h *TestHelper) PipeToHelmRun(stdin string, arg ...string) (string, string, error) {
withParams := append([]string{"--kube-context=" + h.k8sContext}, arg...)
return combinedOutput(stdin, h.helm.path, withParams...)
}
// LinkerdRunStream initiates a linkerd command appended with the
// --linkerd-namespace flag, and returns a Stream that can be used to read the
// command's output while it is still executing.
func (h *TestHelper) LinkerdRunStream(arg ...string) (*Stream, error) {
withParams := append([]string{"--linkerd-namespace", h.namespace, "--context=" + h.k8sContext}, arg...)
cmd := exec.Command(h.linkerd, withParams...)
cmdReader, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
err = cmd.Start()
if err != nil {
return nil, err
}
time.Sleep(500 * time.Millisecond)
if cmd.ProcessState != nil && cmd.ProcessState.Exited() {
return nil, fmt.Errorf("Process exited: %s", cmd.ProcessState)
}
return &Stream{cmd: cmd, out: cmdReader}, nil
}
// KubectlStream initiates a kubectl command appended with the
// --namespace flag, and returns a Stream that can be used to read the
// command's output while it is still executing.
func (h *TestHelper) KubectlStream(arg ...string) (*Stream, error) {
withContext := append([]string{"--namespace", h.namespace, "--context=" + h.k8sContext}, arg...)
cmd := exec.Command("kubectl", withContext...)
cmdReader, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
err = cmd.Start()
if err != nil {
return nil, err
}
time.Sleep(500 * time.Millisecond)
if cmd.ProcessState != nil && cmd.ProcessState.Exited() {
return nil, fmt.Errorf("Process exited: %s", cmd.ProcessState)
}
return &Stream{cmd: cmd, out: cmdReader}, nil
}
// HelmUpgrade runs the helm upgrade subcommand, with the provided arguments
func (h *TestHelper) HelmUpgrade(chart, releaseName string, arg ...string) (string, string, error) {
withParams := append([]string{
"upgrade",
releaseName,
"--kube-context", h.k8sContext,
"--namespace", h.namespace,
"--timeout", "60m",
"--wait",
chart,
}, arg...)
return combinedOutput("", h.helm.path, withParams...)
}
// HelmInstall runs the helm install subcommand, with the provided arguments
func (h *TestHelper) HelmInstall(chart, releaseName string, arg ...string) (string, string, error) {
withParams := append([]string{
"install",
releaseName,
chart,
"--kube-context", h.k8sContext,
"--namespace", h.namespace,
"--create-namespace",
"--timeout", "60m",
"--wait",
}, arg...)
return combinedOutput("", h.helm.path, withParams...)
}
// HelmCmdPlain runs a helm subcommand, with the provided arguments and no defaults
func (h *TestHelper) HelmCmdPlain(cmd, chart, releaseName string, arg ...string) (string, string, error) {
withParams := append([]string{
cmd,
releaseName,
chart,
"--kube-context", h.k8sContext,
}, arg...)
return combinedOutput("", h.helm.path, withParams...)
}
// HelmInstallMulticluster runs the helm install subcommand for multicluster, with the provided arguments
func (h *TestHelper) HelmInstallMulticluster(chart string, arg ...string) (string, string, error) {
withParams := append([]string{
"install",
h.helm.multiclusterReleaseName,
chart,
"--kube-context", h.k8sContext,
"--namespace", h.GetMulticlusterNamespace(),
"--create-namespace",
"--set", "linkerdNamespace=" + h.GetLinkerdNamespace(),
}, arg...)
return combinedOutput("", h.helm.path, withParams...)
}
// HelmUninstallMulticluster runs the helm delete subcommand for multicluster
func (h *TestHelper) HelmUninstallMulticluster(chart string) (string, string, error) {
withParams := []string{
"delete",
h.helm.multiclusterReleaseName,
"--kube-context", h.k8sContext,
}
return combinedOutput("", h.helm.path, withParams...)
}
// ValidateOutput validates a string against the contents of a file in the
// test's testdata directory.
func (h *TestHelper) ValidateOutput(out, fixtureFile string) error {
expected, err := ReadFile("testdata/" + fixtureFile)
if err != nil {
return err
}
if out != expected {
return fmt.Errorf(
"Expected:\n%s\nActual:\n%s", expected, out)
}
return nil
}
// CheckVersion validates the output of the "linkerd version" command.
func (h *TestHelper) CheckVersion(serverVersion string) error {
out, err := h.LinkerdRun("version")
if err != nil {
return err
}
if !strings.Contains(out, fmt.Sprintf("Client version: %s", h.version)) {
return fmt.Errorf("Expected client version [%s], got:\n%s", h.version, out)
}
if !strings.Contains(out, fmt.Sprintf("Server version: %s", serverVersion)) {
return fmt.Errorf("Expected server version [%s], got:\n%s", serverVersion, out)
}
return nil
}
// RetryFor retries a given function every second until the function returns
// without an error, or a timeout is reached. If the timeout is reached, it
// returns the last error received from the function.
func RetryFor(timeout time.Duration, fn func() error) error {
err := fn()
if err == nil {
return nil
}
timeoutAfter := time.NewTimer(timeout)
defer timeoutAfter.Stop()
retryAfter := time.NewTicker(time.Second)
defer retryAfter.Stop()
for {
select {
case <-timeoutAfter.C:
return err
case <-retryAfter.C:
err = fn()
if err == nil {
return nil
}
}
}
}
// HTTPGetURL sends a GET request to the given URL. It returns the response body
// in the event of a successful 200 response. In the event of a non-200
// response, it returns an error. It retries requests for up to 30 seconds,
// giving pods time to start.
func (h *TestHelper) HTTPGetURL(url string) (string, error) {
var body string
err := RetryFor(time.Minute, func() error {
resp, err := h.httpClient.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
bytes, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("Error reading response body: %w", err)
}
body = string(bytes)
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("GET request to %s returned status code %d with body %q", url, resp.StatusCode, body)
}
return nil
})
return body, err
}
// WithDataPlaneNamespace is used to create a test namespace that is deleted before the function returns
func (h *TestHelper) WithDataPlaneNamespace(ctx context.Context, testName string, annotations map[string]string, t *testing.T, test func(t *testing.T, ns string)) {
prefixedNs := h.GetTestNamespace(testName)
if err := h.CreateDataPlaneNamespaceIfNotExists(ctx, prefixedNs, annotations); err != nil {
AnnotatedFatalf(t, fmt.Sprintf("failed to create %s namespace", prefixedNs),
"failed to create %s namespace: %s", prefixedNs, err)
}
test(t, prefixedNs)
if err := h.DeleteNamespaceIfExists(ctx, prefixedNs); err != nil {
AnnotatedFatalf(t, fmt.Sprintf("failed to delete %s namespace", prefixedNs),
"failed to delete %s namespace: %s", prefixedNs, err)
}
}
// GetReleaseChannelVersions is used to fetch the latest versions for Linkerd's
// release channels: edge and stable
func (h *TestHelper) GetReleaseChannelVersions() (map[string]string, error) {
url := "https://versioncheck.linkerd.io/version.json"
resp, err := h.httpClient.Get(url)
if err != nil {
return map[string]string{}, err
}
defer resp.Body.Close()
var versions map[string]string
if err := json.NewDecoder(resp.Body).Decode(&versions); err != nil {
return map[string]string{}, err
}
return versions, nil
}
// DownloadCLIBinary is used to download the Linkerd CLI from GitHub Releases
// page. The method takes the version to download and a filepath where to save
// the binary.
func (h *TestHelper) DownloadCLIBinary(filepath, version string) error {
url := fmt.Sprintf("https://github.com/linkerd/linkerd2/releases/download/%[1]s/linkerd2-cli-%[1]s-%s-%s", version, runtime.GOOS, runtime.GOARCH)
resp, err := h.httpClient.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
// Create if it doesn't already exist
// The CLI binary needs to be executable so we ignore lint errors about
// file permissions.
//nolint:gosec
out, err := os.OpenFile(filepath, os.O_RDWR|os.O_CREATE, 0500)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, resp.Body)
return err
}
// ReadFile reads a file from disk and returns the contents as a string.
func ReadFile(file string) (string, error) {
b, err := os.ReadFile(file)
if err != nil {
return "", err
}
return string(b), nil
}
// combinedOutput executes a shell command and returns the output.
func combinedOutput(stdin string, name string, arg ...string) (string, string, error) {
command := exec.Command(name, arg...)
command.Stdin = strings.NewReader(stdin)
var stderr bytes.Buffer
command.Stderr = &stderr
stdout, err := command.Output()
return string(stdout), stderr.String(), err
}
// RowStat is used to store the contents for a single row from the stat command
type RowStat struct {
Name string
Status string
Meshed string
Success string
Rps string
P50Latency string
P95Latency string
P99Latency string
TCPOpenConnections string
UnauthorizedRPS string
}
// CheckRowCount checks that expectedRowCount rows have been returned
func CheckRowCount(out string, expectedRowCount int) ([]string, error) {
rows := strings.Split(strings.TrimSuffix(out, "\n"), "\n")
if len(rows) < 2 {
return nil, fmt.Errorf(
"Expected at least 2 lines in %q",
out,
)
}
rows = rows[1:] // strip header
if len(rows) != expectedRowCount {
return nil, fmt.Errorf(
"Expected %d rows in stat output but got %d in %q",
expectedRowCount, len(rows), out)
}
return rows, nil
}
// ParseRows parses the output of linkerd stat into a map of resource names to RowStat objects
func ParseRows(out string, expectedRowCount, expectedColumnCount int) (map[string]*RowStat, error) {
rows, err := CheckRowCount(out, expectedRowCount)
if err != nil {
return nil, err
}
rowStats := make(map[string]*RowStat)
for _, row := range rows {
fields := strings.Fields(row)
if expectedColumnCount == 0 {
expectedColumnCount = 8
}
if len(fields) != expectedColumnCount {
return nil, fmt.Errorf(
"Expected %d columns in stat output but got %d in %q",
expectedColumnCount, len(fields), row)
}
rowStats[fields[0]] = &RowStat{
Name: fields[0],
}
i := 0
if expectedColumnCount == 9 {
rowStats[fields[0]].Status = fields[1]
i = 1
}
rowStats[fields[0]].Meshed = fields[1+i]
rowStats[fields[0]].Success = fields[2+i]
rowStats[fields[0]].Rps = fields[3+i]
rowStats[fields[0]].P50Latency = fields[4+i]
rowStats[fields[0]].P95Latency = fields[5+i]
rowStats[fields[0]].P99Latency = fields[6+i]
if 7+i < len(fields) {
rowStats[fields[0]].TCPOpenConnections = fields[7+i]
}
}
return rowStats, nil
}
// ParseEvents parses the output of kubectl events
func ParseEvents(out string) ([]*corev1.Event, error) {
var list corev1.List
if err := json.Unmarshal([]byte(out), &list); err != nil {
return nil, fmt.Errorf("error unmarshaling list from `kubectl get events`: %w", err)
}
if len(list.Items) == 0 {
return nil, errors.New("no events found")
}
var events []*corev1.Event
for _, i := range list.Items {
var e corev1.Event
if err := json.Unmarshal(i.Raw, &e); err != nil {
return nil, fmt.Errorf("error unmarshaling list event from `kubectl get events`: %w", err)
}
events = append(events, &e)
}
return events, nil
}
package testutil
import (
"encoding/json"
"errors"
"fmt"
"io"
"strings"
"time"
"github.com/linkerd/linkerd2/pkg/healthcheck"
vizHealthcheck "github.com/linkerd/linkerd2/viz/pkg/healthcheck"
)
var preCategories = []healthcheck.CategoryID{
healthcheck.KubernetesAPIChecks,
healthcheck.KubernetesVersionChecks,
healthcheck.LinkerdPreInstallChecks,
healthcheck.LinkerdVersionChecks,
}
var coreCategories = []healthcheck.CategoryID{
healthcheck.KubernetesAPIChecks,
healthcheck.KubernetesVersionChecks,
healthcheck.LinkerdControlPlaneExistenceChecks,
healthcheck.LinkerdConfigChecks,
healthcheck.LinkerdIdentity,
healthcheck.LinkerdWebhooksAndAPISvcTLS,
healthcheck.LinkerdVersionChecks,
healthcheck.LinkerdControlPlaneProxyChecks,
}
var dataPlaneCategories = []healthcheck.CategoryID{
healthcheck.LinkerdIdentityDataPlane,
healthcheck.LinkerdControlPlaneProxyChecks,
healthcheck.LinkerdDataPlaneChecks,
}
// TestCheckPre runs validates the output of `linkerd check --pre`
func (h *TestHelper) TestCheckPre() error {
cmd := []string{"check", "--pre", "--output", "json", "--wait", "5m"}
return h.testCheck(cmd, preCategories)
}
// TestCheck runs validates the output of `linkerd check`
func (h *TestHelper) TestCheck(extraArgs ...string) error {
return h.TestCheckWith([]healthcheck.CategoryID{healthcheck.LinkerdControlPlaneVersionChecks, vizHealthcheck.LinkerdVizExtensionCheck}, extraArgs...)
}
// TestCheckWith validates the output of `linkerd check`. It will validate the
// core categories and any additional categories that the caller provides.
func (h *TestHelper) TestCheckWith(additional []healthcheck.CategoryID, extraArgs ...string) error {
cmd := []string{"check", "--output", "json", "--wait", "5m"}
cmd = append(cmd, extraArgs...)
categories := append(coreCategories, additional...)
return h.testCheck(cmd, categories)
}
// TestCheckProxy runs validates the output of `linkerd check --proxy`
func (h *TestHelper) TestCheckProxy(expectedVersion, namespace string) error {
cmd := []string{"check", "--proxy", "--expected-version", expectedVersion,
"--namespace", namespace, "--output", "json", "--wait", "5m"}
categories := append(coreCategories, vizHealthcheck.LinkerdVizExtensionCheck,
vizHealthcheck.LinkerdVizExtensionDataPlaneCheck)
categories = append(categories, dataPlaneCategories...)
return h.testCheck(cmd, categories)
}
func (h *TestHelper) testCheck(cmd []string, categories []healthcheck.CategoryID) error {
timeout := time.Minute * 10
return RetryFor(timeout, func() error {
res, err := h.LinkerdRun(cmd...)
if err != nil {
return fmt.Errorf("'linkerd check' command failed\n%w\n%s", err, res)
}
returnedCats := map[healthcheck.CategoryID]struct{}{}
// We can't just use json.Unmarshal() because the check output is formatted as NDJSON
d := json.NewDecoder(strings.NewReader(res))
for {
var out healthcheck.CheckOutput
err := d.Decode(&out)
if err != nil {
// io.EOF is expected at end of stream.
if !errors.Is(err, io.EOF) {
return fmt.Errorf("error processing 'linkerd check' output: %w", err)
}
break
}
errs := []string{}
for _, cat := range out.Categories {
for _, check := range cat.Checks {
returnedCats[cat.Name] = struct{}{}
if check.Result == healthcheck.CheckErr {
errs = append(errs, fmt.Sprintf("%s: %s", cat.Name, check.Error))
}
}
}
if len(errs) > 0 {
return errors.New(strings.Join(errs, "\n"))
}
}
errs := []string{}
for _, cat := range categories {
if _, ok := returnedCats[cat]; !ok {
errs = append(errs, fmt.Sprintf("missing category '%s'", cat))
}
}
if len(errs) > 0 {
return errors.New(strings.Join(errs, "\n"))
}
return nil
})
}
package client
import (
"context"
"github.com/linkerd/linkerd2/pkg/k8s"
pb "github.com/linkerd/linkerd2/viz/metrics-api/gen/viz"
"go.opencensus.io/plugin/ocgrpc"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
const (
apiPort = 8085
apiDeployment = "metrics-api"
)
// NewInternalClient creates a new Viz API client intended to run inside a
// Kubernetes cluster.
func NewInternalClient(addr string) (pb.ApiClient, error) {
conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithStatsHandler(&ocgrpc.ClientHandler{}))
if err != nil {
return nil, err
}
return pb.NewApiClient(conn), nil
}
// NewExternalClient creates a new Viz API client intended to run from
// outside a Kubernetes cluster.
func NewExternalClient(ctx context.Context, namespace string, kubeAPI *k8s.KubernetesAPI) (pb.ApiClient, error) {
portforward, err := k8s.NewPortForward(
ctx,
kubeAPI,
namespace,
apiDeployment,
"localhost",
0,
apiPort,
false,
)
if err != nil {
return nil, err
}
addr := portforward.AddressAndPort()
if err = portforward.Init(); err != nil {
return nil, err
}
conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithStatsHandler(&ocgrpc.ClientHandler{}))
if err != nil {
return nil, err
}
return pb.NewApiClient(conn), nil
}
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc v3.20.0
// source: viz.proto
package viz
import (
duration "github.com/golang/protobuf/ptypes/duration"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type CheckStatus int32
const (
CheckStatus_OK CheckStatus = 0
CheckStatus_FAIL CheckStatus = 1
CheckStatus_ERROR CheckStatus = 2
)
// Enum value maps for CheckStatus.
var (
CheckStatus_name = map[int32]string{
0: "OK",
1: "FAIL",
2: "ERROR",
}
CheckStatus_value = map[string]int32{
"OK": 0,
"FAIL": 1,
"ERROR": 2,
}
)
func (x CheckStatus) Enum() *CheckStatus {
p := new(CheckStatus)
*p = x
return p
}
func (x CheckStatus) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (CheckStatus) Descriptor() protoreflect.EnumDescriptor {
return file_viz_proto_enumTypes[0].Descriptor()
}
func (CheckStatus) Type() protoreflect.EnumType {
return &file_viz_proto_enumTypes[0]
}
func (x CheckStatus) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use CheckStatus.Descriptor instead.
func (CheckStatus) EnumDescriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{0}
}
type HttpMethod_Registered int32
const (
HttpMethod_GET HttpMethod_Registered = 0
HttpMethod_POST HttpMethod_Registered = 1
HttpMethod_PUT HttpMethod_Registered = 2
HttpMethod_DELETE HttpMethod_Registered = 3
HttpMethod_PATCH HttpMethod_Registered = 4
HttpMethod_OPTIONS HttpMethod_Registered = 5
HttpMethod_CONNECT HttpMethod_Registered = 6
HttpMethod_HEAD HttpMethod_Registered = 7
HttpMethod_TRACE HttpMethod_Registered = 8
)
// Enum value maps for HttpMethod_Registered.
var (
HttpMethod_Registered_name = map[int32]string{
0: "GET",
1: "POST",
2: "PUT",
3: "DELETE",
4: "PATCH",
5: "OPTIONS",
6: "CONNECT",
7: "HEAD",
8: "TRACE",
}
HttpMethod_Registered_value = map[string]int32{
"GET": 0,
"POST": 1,
"PUT": 2,
"DELETE": 3,
"PATCH": 4,
"OPTIONS": 5,
"CONNECT": 6,
"HEAD": 7,
"TRACE": 8,
}
)
func (x HttpMethod_Registered) Enum() *HttpMethod_Registered {
p := new(HttpMethod_Registered)
*p = x
return p
}
func (x HttpMethod_Registered) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (HttpMethod_Registered) Descriptor() protoreflect.EnumDescriptor {
return file_viz_proto_enumTypes[1].Descriptor()
}
func (HttpMethod_Registered) Type() protoreflect.EnumType {
return &file_viz_proto_enumTypes[1]
}
func (x HttpMethod_Registered) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use HttpMethod_Registered.Descriptor instead.
func (HttpMethod_Registered) EnumDescriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{10, 0}
}
type Scheme_Registered int32
const (
Scheme_HTTP Scheme_Registered = 0
Scheme_HTTPS Scheme_Registered = 1
)
// Enum value maps for Scheme_Registered.
var (
Scheme_Registered_name = map[int32]string{
0: "HTTP",
1: "HTTPS",
}
Scheme_Registered_value = map[string]int32{
"HTTP": 0,
"HTTPS": 1,
}
)
func (x Scheme_Registered) Enum() *Scheme_Registered {
p := new(Scheme_Registered)
*p = x
return p
}
func (x Scheme_Registered) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (Scheme_Registered) Descriptor() protoreflect.EnumDescriptor {
return file_viz_proto_enumTypes[2].Descriptor()
}
func (Scheme_Registered) Type() protoreflect.EnumType {
return &file_viz_proto_enumTypes[2]
}
func (x Scheme_Registered) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use Scheme_Registered.Descriptor instead.
func (Scheme_Registered) EnumDescriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{11, 0}
}
type Empty struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *Empty) Reset() {
*x = Empty{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Empty) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Empty) ProtoMessage() {}
func (x *Empty) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Empty.ProtoReflect.Descriptor instead.
func (*Empty) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{0}
}
type CheckResult struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
SubsystemName string `protobuf:"bytes,1,opt,name=SubsystemName,proto3" json:"SubsystemName,omitempty"`
CheckDescription string `protobuf:"bytes,2,opt,name=CheckDescription,proto3" json:"CheckDescription,omitempty"`
Status CheckStatus `protobuf:"varint,3,opt,name=Status,proto3,enum=linkerd2.viz.CheckStatus" json:"Status,omitempty"`
FriendlyMessageToUser string `protobuf:"bytes,4,opt,name=FriendlyMessageToUser,proto3" json:"FriendlyMessageToUser,omitempty"`
}
func (x *CheckResult) Reset() {
*x = CheckResult{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *CheckResult) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CheckResult) ProtoMessage() {}
func (x *CheckResult) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CheckResult.ProtoReflect.Descriptor instead.
func (*CheckResult) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{1}
}
func (x *CheckResult) GetSubsystemName() string {
if x != nil {
return x.SubsystemName
}
return ""
}
func (x *CheckResult) GetCheckDescription() string {
if x != nil {
return x.CheckDescription
}
return ""
}
func (x *CheckResult) GetStatus() CheckStatus {
if x != nil {
return x.Status
}
return CheckStatus_OK
}
func (x *CheckResult) GetFriendlyMessageToUser() string {
if x != nil {
return x.FriendlyMessageToUser
}
return ""
}
type SelfCheckRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *SelfCheckRequest) Reset() {
*x = SelfCheckRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SelfCheckRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SelfCheckRequest) ProtoMessage() {}
func (x *SelfCheckRequest) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SelfCheckRequest.ProtoReflect.Descriptor instead.
func (*SelfCheckRequest) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{2}
}
type SelfCheckResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Results []*CheckResult `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"`
}
func (x *SelfCheckResponse) Reset() {
*x = SelfCheckResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SelfCheckResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SelfCheckResponse) ProtoMessage() {}
func (x *SelfCheckResponse) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SelfCheckResponse.ProtoReflect.Descriptor instead.
func (*SelfCheckResponse) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{3}
}
func (x *SelfCheckResponse) GetResults() []*CheckResult {
if x != nil {
return x.Results
}
return nil
}
type ListServicesRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Namespace string `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"`
}
func (x *ListServicesRequest) Reset() {
*x = ListServicesRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListServicesRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListServicesRequest) ProtoMessage() {}
func (x *ListServicesRequest) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListServicesRequest.ProtoReflect.Descriptor instead.
func (*ListServicesRequest) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{4}
}
func (x *ListServicesRequest) GetNamespace() string {
if x != nil {
return x.Namespace
}
return ""
}
type ListServicesResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Services []*Service `protobuf:"bytes,1,rep,name=services,proto3" json:"services,omitempty"`
}
func (x *ListServicesResponse) Reset() {
*x = ListServicesResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListServicesResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListServicesResponse) ProtoMessage() {}
func (x *ListServicesResponse) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListServicesResponse.ProtoReflect.Descriptor instead.
func (*ListServicesResponse) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{5}
}
func (x *ListServicesResponse) GetServices() []*Service {
if x != nil {
return x.Services
}
return nil
}
type Service struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Namespace string `protobuf:"bytes,2,opt,name=namespace,proto3" json:"namespace,omitempty"`
}
func (x *Service) Reset() {
*x = Service{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Service) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Service) ProtoMessage() {}
func (x *Service) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Service.ProtoReflect.Descriptor instead.
func (*Service) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{6}
}
func (x *Service) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *Service) GetNamespace() string {
if x != nil {
return x.Namespace
}
return ""
}
type ListPodsRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Selector *ResourceSelection `protobuf:"bytes,2,opt,name=selector,proto3" json:"selector,omitempty"`
}
func (x *ListPodsRequest) Reset() {
*x = ListPodsRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListPodsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListPodsRequest) ProtoMessage() {}
func (x *ListPodsRequest) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListPodsRequest.ProtoReflect.Descriptor instead.
func (*ListPodsRequest) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{7}
}
func (x *ListPodsRequest) GetSelector() *ResourceSelection {
if x != nil {
return x.Selector
}
return nil
}
type ListPodsResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Pods []*Pod `protobuf:"bytes,1,rep,name=pods,proto3" json:"pods,omitempty"`
}
func (x *ListPodsResponse) Reset() {
*x = ListPodsResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListPodsResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListPodsResponse) ProtoMessage() {}
func (x *ListPodsResponse) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListPodsResponse.ProtoReflect.Descriptor instead.
func (*ListPodsResponse) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{8}
}
func (x *ListPodsResponse) GetPods() []*Pod {
if x != nil {
return x.Pods
}
return nil
}
type Pod struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
PodIP string `protobuf:"bytes,2,opt,name=podIP,proto3" json:"podIP,omitempty"`
// Types that are assignable to Owner:
//
// *Pod_Deployment
// *Pod_ReplicaSet
// *Pod_ReplicationController
// *Pod_StatefulSet
// *Pod_DaemonSet
// *Pod_Job
Owner isPod_Owner `protobuf_oneof:"owner"`
Status string `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty"`
Added bool `protobuf:"varint,5,opt,name=added,proto3" json:"added,omitempty"` // true if this pod has a proxy sidecar (data plane)
SinceLastReport *duration.Duration `protobuf:"bytes,6,opt,name=sinceLastReport,proto3" json:"sinceLastReport,omitempty"`
ControllerNamespace string `protobuf:"bytes,7,opt,name=controllerNamespace,proto3" json:"controllerNamespace,omitempty"` // namespace of controller this pod reports to
ControlPlane bool `protobuf:"varint,8,opt,name=controlPlane,proto3" json:"controlPlane,omitempty"` // true if this pod is part of the control plane
Uptime *duration.Duration `protobuf:"bytes,9,opt,name=uptime,proto3" json:"uptime,omitempty"` // uptime of this pod
ProxyReady bool `protobuf:"varint,15,opt,name=proxyReady,proto3" json:"proxyReady,omitempty"` // true if this pod has proxy container and that one is in ready state
ProxyVersion string `protobuf:"bytes,16,opt,name=proxyVersion,proto3" json:"proxyVersion,omitempty"` // version of the proxy if present
ResourceVersion string `protobuf:"bytes,17,opt,name=resourceVersion,proto3" json:"resourceVersion,omitempty"` // resource version in the Kubernetes API
}
func (x *Pod) Reset() {
*x = Pod{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Pod) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Pod) ProtoMessage() {}
func (x *Pod) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[9]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Pod.ProtoReflect.Descriptor instead.
func (*Pod) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{9}
}
func (x *Pod) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *Pod) GetPodIP() string {
if x != nil {
return x.PodIP
}
return ""
}
func (m *Pod) GetOwner() isPod_Owner {
if m != nil {
return m.Owner
}
return nil
}
func (x *Pod) GetDeployment() string {
if x, ok := x.GetOwner().(*Pod_Deployment); ok {
return x.Deployment
}
return ""
}
func (x *Pod) GetReplicaSet() string {
if x, ok := x.GetOwner().(*Pod_ReplicaSet); ok {
return x.ReplicaSet
}
return ""
}
func (x *Pod) GetReplicationController() string {
if x, ok := x.GetOwner().(*Pod_ReplicationController); ok {
return x.ReplicationController
}
return ""
}
func (x *Pod) GetStatefulSet() string {
if x, ok := x.GetOwner().(*Pod_StatefulSet); ok {
return x.StatefulSet
}
return ""
}
func (x *Pod) GetDaemonSet() string {
if x, ok := x.GetOwner().(*Pod_DaemonSet); ok {
return x.DaemonSet
}
return ""
}
func (x *Pod) GetJob() string {
if x, ok := x.GetOwner().(*Pod_Job); ok {
return x.Job
}
return ""
}
func (x *Pod) GetStatus() string {
if x != nil {
return x.Status
}
return ""
}
func (x *Pod) GetAdded() bool {
if x != nil {
return x.Added
}
return false
}
func (x *Pod) GetSinceLastReport() *duration.Duration {
if x != nil {
return x.SinceLastReport
}
return nil
}
func (x *Pod) GetControllerNamespace() string {
if x != nil {
return x.ControllerNamespace
}
return ""
}
func (x *Pod) GetControlPlane() bool {
if x != nil {
return x.ControlPlane
}
return false
}
func (x *Pod) GetUptime() *duration.Duration {
if x != nil {
return x.Uptime
}
return nil
}
func (x *Pod) GetProxyReady() bool {
if x != nil {
return x.ProxyReady
}
return false
}
func (x *Pod) GetProxyVersion() string {
if x != nil {
return x.ProxyVersion
}
return ""
}
func (x *Pod) GetResourceVersion() string {
if x != nil {
return x.ResourceVersion
}
return ""
}
type isPod_Owner interface {
isPod_Owner()
}
type Pod_Deployment struct {
Deployment string `protobuf:"bytes,3,opt,name=deployment,proto3,oneof"`
}
type Pod_ReplicaSet struct {
ReplicaSet string `protobuf:"bytes,10,opt,name=replica_set,json=replicaSet,proto3,oneof"`
}
type Pod_ReplicationController struct {
ReplicationController string `protobuf:"bytes,11,opt,name=replication_controller,json=replicationController,proto3,oneof"`
}
type Pod_StatefulSet struct {
StatefulSet string `protobuf:"bytes,12,opt,name=stateful_set,json=statefulSet,proto3,oneof"`
}
type Pod_DaemonSet struct {
DaemonSet string `protobuf:"bytes,13,opt,name=daemon_set,json=daemonSet,proto3,oneof"`
}
type Pod_Job struct {
Job string `protobuf:"bytes,14,opt,name=job,proto3,oneof"`
}
func (*Pod_Deployment) isPod_Owner() {}
func (*Pod_ReplicaSet) isPod_Owner() {}
func (*Pod_ReplicationController) isPod_Owner() {}
func (*Pod_StatefulSet) isPod_Owner() {}
func (*Pod_DaemonSet) isPod_Owner() {}
func (*Pod_Job) isPod_Owner() {}
type HttpMethod struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Type:
//
// *HttpMethod_Registered_
// *HttpMethod_Unregistered
Type isHttpMethod_Type `protobuf_oneof:"type"`
}
func (x *HttpMethod) Reset() {
*x = HttpMethod{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HttpMethod) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HttpMethod) ProtoMessage() {}
func (x *HttpMethod) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[10]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HttpMethod.ProtoReflect.Descriptor instead.
func (*HttpMethod) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{10}
}
func (m *HttpMethod) GetType() isHttpMethod_Type {
if m != nil {
return m.Type
}
return nil
}
func (x *HttpMethod) GetRegistered() HttpMethod_Registered {
if x, ok := x.GetType().(*HttpMethod_Registered_); ok {
return x.Registered
}
return HttpMethod_GET
}
func (x *HttpMethod) GetUnregistered() string {
if x, ok := x.GetType().(*HttpMethod_Unregistered); ok {
return x.Unregistered
}
return ""
}
type isHttpMethod_Type interface {
isHttpMethod_Type()
}
type HttpMethod_Registered_ struct {
Registered HttpMethod_Registered `protobuf:"varint,1,opt,name=registered,proto3,enum=linkerd2.viz.HttpMethod_Registered,oneof"`
}
type HttpMethod_Unregistered struct {
Unregistered string `protobuf:"bytes,2,opt,name=unregistered,proto3,oneof"`
}
func (*HttpMethod_Registered_) isHttpMethod_Type() {}
func (*HttpMethod_Unregistered) isHttpMethod_Type() {}
type Scheme struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Type:
//
// *Scheme_Registered_
// *Scheme_Unregistered
Type isScheme_Type `protobuf_oneof:"type"`
}
func (x *Scheme) Reset() {
*x = Scheme{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Scheme) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Scheme) ProtoMessage() {}
func (x *Scheme) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[11]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Scheme.ProtoReflect.Descriptor instead.
func (*Scheme) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{11}
}
func (m *Scheme) GetType() isScheme_Type {
if m != nil {
return m.Type
}
return nil
}
func (x *Scheme) GetRegistered() Scheme_Registered {
if x, ok := x.GetType().(*Scheme_Registered_); ok {
return x.Registered
}
return Scheme_HTTP
}
func (x *Scheme) GetUnregistered() string {
if x, ok := x.GetType().(*Scheme_Unregistered); ok {
return x.Unregistered
}
return ""
}
type isScheme_Type interface {
isScheme_Type()
}
type Scheme_Registered_ struct {
Registered Scheme_Registered `protobuf:"varint,1,opt,name=registered,proto3,enum=linkerd2.viz.Scheme_Registered,oneof"`
}
type Scheme_Unregistered struct {
Unregistered string `protobuf:"bytes,2,opt,name=unregistered,proto3,oneof"`
}
func (*Scheme_Registered_) isScheme_Type() {}
func (*Scheme_Unregistered) isScheme_Type() {}
type Headers struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Headers []*Headers_Header `protobuf:"bytes,1,rep,name=headers,proto3" json:"headers,omitempty"`
}
func (x *Headers) Reset() {
*x = Headers{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Headers) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Headers) ProtoMessage() {}
func (x *Headers) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[12]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Headers.ProtoReflect.Descriptor instead.
func (*Headers) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{12}
}
func (x *Headers) GetHeaders() []*Headers_Header {
if x != nil {
return x.Headers
}
return nil
}
type Eos struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to End:
//
// *Eos_GrpcStatusCode
// *Eos_ResetErrorCode
End isEos_End `protobuf_oneof:"end"`
}
func (x *Eos) Reset() {
*x = Eos{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[13]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Eos) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Eos) ProtoMessage() {}
func (x *Eos) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[13]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Eos.ProtoReflect.Descriptor instead.
func (*Eos) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{13}
}
func (m *Eos) GetEnd() isEos_End {
if m != nil {
return m.End
}
return nil
}
func (x *Eos) GetGrpcStatusCode() uint32 {
if x, ok := x.GetEnd().(*Eos_GrpcStatusCode); ok {
return x.GrpcStatusCode
}
return 0
}
func (x *Eos) GetResetErrorCode() uint32 {
if x, ok := x.GetEnd().(*Eos_ResetErrorCode); ok {
return x.ResetErrorCode
}
return 0
}
type isEos_End interface {
isEos_End()
}
type Eos_GrpcStatusCode struct {
GrpcStatusCode uint32 `protobuf:"varint,1,opt,name=grpc_status_code,json=grpcStatusCode,proto3,oneof"`
}
type Eos_ResetErrorCode struct {
ResetErrorCode uint32 `protobuf:"varint,2,opt,name=reset_error_code,json=resetErrorCode,proto3,oneof"`
}
func (*Eos_GrpcStatusCode) isEos_End() {}
func (*Eos_ResetErrorCode) isEos_End() {}
type ApiError struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Error string `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"`
}
func (x *ApiError) Reset() {
*x = ApiError{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[14]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ApiError) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ApiError) ProtoMessage() {}
func (x *ApiError) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[14]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ApiError.ProtoReflect.Descriptor instead.
func (*ApiError) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{14}
}
func (x *ApiError) GetError() string {
if x != nil {
return x.Error
}
return ""
}
type PodErrors struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Errors []*PodErrors_PodError `protobuf:"bytes,1,rep,name=errors,proto3" json:"errors,omitempty"`
}
func (x *PodErrors) Reset() {
*x = PodErrors{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[15]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PodErrors) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PodErrors) ProtoMessage() {}
func (x *PodErrors) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[15]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PodErrors.ProtoReflect.Descriptor instead.
func (*PodErrors) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{15}
}
func (x *PodErrors) GetErrors() []*PodErrors_PodError {
if x != nil {
return x.Errors
}
return nil
}
type Resource struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The namespace the resource is in.
//
// If empty, indicates all namespaces should be considered.
Namespace string `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"`
// The type of resource.
//
// This can be:
// - "all" -- includes all Kubernetes resource types only
// - "authority" -- a special resource type derived from request `:authority` values
// - Otherwise, the resource type may be any Kubernetes resource (e.g. "namespace", "deployment").
Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"`
// An optional resource name.
Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"`
}
func (x *Resource) Reset() {
*x = Resource{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[16]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Resource) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Resource) ProtoMessage() {}
func (x *Resource) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[16]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Resource.ProtoReflect.Descriptor instead.
func (*Resource) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{16}
}
func (x *Resource) GetNamespace() string {
if x != nil {
return x.Namespace
}
return ""
}
func (x *Resource) GetType() string {
if x != nil {
return x.Type
}
return ""
}
func (x *Resource) GetName() string {
if x != nil {
return x.Name
}
return ""
}
type ResourceSelection struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Identifies a Kubernetes resource.
Resource *Resource `protobuf:"bytes,1,opt,name=resource,proto3" json:"resource,omitempty"`
// A string-formatted Kubernetes label selector as passed to `kubectl get
// --selector`.
//
// XXX in the future this may be superseded by a data structure that more
// richly describes a parsed label selector.
LabelSelector string `protobuf:"bytes,2,opt,name=label_selector,json=labelSelector,proto3" json:"label_selector,omitempty"`
}
func (x *ResourceSelection) Reset() {
*x = ResourceSelection{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[17]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ResourceSelection) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ResourceSelection) ProtoMessage() {}
func (x *ResourceSelection) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[17]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ResourceSelection.ProtoReflect.Descriptor instead.
func (*ResourceSelection) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{17}
}
func (x *ResourceSelection) GetResource() *Resource {
if x != nil {
return x.Resource
}
return nil
}
func (x *ResourceSelection) GetLabelSelector() string {
if x != nil {
return x.LabelSelector
}
return ""
}
type ResourceError struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Resource *Resource `protobuf:"bytes,1,opt,name=resource,proto3" json:"resource,omitempty"`
Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"`
}
func (x *ResourceError) Reset() {
*x = ResourceError{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[18]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ResourceError) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ResourceError) ProtoMessage() {}
func (x *ResourceError) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[18]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ResourceError.ProtoReflect.Descriptor instead.
func (*ResourceError) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{18}
}
func (x *ResourceError) GetResource() *Resource {
if x != nil {
return x.Resource
}
return nil
}
func (x *ResourceError) GetError() string {
if x != nil {
return x.Error
}
return ""
}
type StatSummaryRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Selector *ResourceSelection `protobuf:"bytes,1,opt,name=selector,proto3" json:"selector,omitempty"`
TimeWindow string `protobuf:"bytes,2,opt,name=time_window,json=timeWindow,proto3" json:"time_window,omitempty"`
// Types that are assignable to Outbound:
//
// *StatSummaryRequest_None
// *StatSummaryRequest_ToResource
// *StatSummaryRequest_FromResource
Outbound isStatSummaryRequest_Outbound `protobuf_oneof:"outbound"`
SkipStats bool `protobuf:"varint,6,opt,name=skip_stats,json=skipStats,proto3" json:"skip_stats,omitempty"` // true if we want to skip stats from Prometheus
TcpStats bool `protobuf:"varint,7,opt,name=tcp_stats,json=tcpStats,proto3" json:"tcp_stats,omitempty"`
}
func (x *StatSummaryRequest) Reset() {
*x = StatSummaryRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[19]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *StatSummaryRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*StatSummaryRequest) ProtoMessage() {}
func (x *StatSummaryRequest) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[19]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use StatSummaryRequest.ProtoReflect.Descriptor instead.
func (*StatSummaryRequest) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{19}
}
func (x *StatSummaryRequest) GetSelector() *ResourceSelection {
if x != nil {
return x.Selector
}
return nil
}
func (x *StatSummaryRequest) GetTimeWindow() string {
if x != nil {
return x.TimeWindow
}
return ""
}
func (m *StatSummaryRequest) GetOutbound() isStatSummaryRequest_Outbound {
if m != nil {
return m.Outbound
}
return nil
}
func (x *StatSummaryRequest) GetNone() *Empty {
if x, ok := x.GetOutbound().(*StatSummaryRequest_None); ok {
return x.None
}
return nil
}
func (x *StatSummaryRequest) GetToResource() *Resource {
if x, ok := x.GetOutbound().(*StatSummaryRequest_ToResource); ok {
return x.ToResource
}
return nil
}
func (x *StatSummaryRequest) GetFromResource() *Resource {
if x, ok := x.GetOutbound().(*StatSummaryRequest_FromResource); ok {
return x.FromResource
}
return nil
}
func (x *StatSummaryRequest) GetSkipStats() bool {
if x != nil {
return x.SkipStats
}
return false
}
func (x *StatSummaryRequest) GetTcpStats() bool {
if x != nil {
return x.TcpStats
}
return false
}
type isStatSummaryRequest_Outbound interface {
isStatSummaryRequest_Outbound()
}
type StatSummaryRequest_None struct {
None *Empty `protobuf:"bytes,3,opt,name=none,proto3,oneof"`
}
type StatSummaryRequest_ToResource struct {
ToResource *Resource `protobuf:"bytes,4,opt,name=to_resource,json=toResource,proto3,oneof"`
}
type StatSummaryRequest_FromResource struct {
FromResource *Resource `protobuf:"bytes,5,opt,name=from_resource,json=fromResource,proto3,oneof"`
}
func (*StatSummaryRequest_None) isStatSummaryRequest_Outbound() {}
func (*StatSummaryRequest_ToResource) isStatSummaryRequest_Outbound() {}
func (*StatSummaryRequest_FromResource) isStatSummaryRequest_Outbound() {}
type StatSummaryResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Response:
//
// *StatSummaryResponse_Ok_
// *StatSummaryResponse_Error
Response isStatSummaryResponse_Response `protobuf_oneof:"response"`
}
func (x *StatSummaryResponse) Reset() {
*x = StatSummaryResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[20]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *StatSummaryResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*StatSummaryResponse) ProtoMessage() {}
func (x *StatSummaryResponse) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[20]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use StatSummaryResponse.ProtoReflect.Descriptor instead.
func (*StatSummaryResponse) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{20}
}
func (m *StatSummaryResponse) GetResponse() isStatSummaryResponse_Response {
if m != nil {
return m.Response
}
return nil
}
func (x *StatSummaryResponse) GetOk() *StatSummaryResponse_Ok {
if x, ok := x.GetResponse().(*StatSummaryResponse_Ok_); ok {
return x.Ok
}
return nil
}
func (x *StatSummaryResponse) GetError() *ResourceError {
if x, ok := x.GetResponse().(*StatSummaryResponse_Error); ok {
return x.Error
}
return nil
}
type isStatSummaryResponse_Response interface {
isStatSummaryResponse_Response()
}
type StatSummaryResponse_Ok_ struct {
Ok *StatSummaryResponse_Ok `protobuf:"bytes,1,opt,name=ok,proto3,oneof"`
}
type StatSummaryResponse_Error struct {
Error *ResourceError `protobuf:"bytes,2,opt,name=error,proto3,oneof"`
}
func (*StatSummaryResponse_Ok_) isStatSummaryResponse_Response() {}
func (*StatSummaryResponse_Error) isStatSummaryResponse_Response() {}
type AuthzRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Resource *Resource `protobuf:"bytes,1,opt,name=resource,proto3" json:"resource,omitempty"`
TimeWindow string `protobuf:"bytes,2,opt,name=time_window,json=timeWindow,proto3" json:"time_window,omitempty"`
}
func (x *AuthzRequest) Reset() {
*x = AuthzRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[21]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AuthzRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AuthzRequest) ProtoMessage() {}
func (x *AuthzRequest) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[21]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AuthzRequest.ProtoReflect.Descriptor instead.
func (*AuthzRequest) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{21}
}
func (x *AuthzRequest) GetResource() *Resource {
if x != nil {
return x.Resource
}
return nil
}
func (x *AuthzRequest) GetTimeWindow() string {
if x != nil {
return x.TimeWindow
}
return ""
}
type AuthzResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Response:
//
// *AuthzResponse_Ok_
// *AuthzResponse_Error
Response isAuthzResponse_Response `protobuf_oneof:"response"`
}
func (x *AuthzResponse) Reset() {
*x = AuthzResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[22]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AuthzResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AuthzResponse) ProtoMessage() {}
func (x *AuthzResponse) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[22]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AuthzResponse.ProtoReflect.Descriptor instead.
func (*AuthzResponse) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{22}
}
func (m *AuthzResponse) GetResponse() isAuthzResponse_Response {
if m != nil {
return m.Response
}
return nil
}
func (x *AuthzResponse) GetOk() *AuthzResponse_Ok {
if x, ok := x.GetResponse().(*AuthzResponse_Ok_); ok {
return x.Ok
}
return nil
}
func (x *AuthzResponse) GetError() *ResourceError {
if x, ok := x.GetResponse().(*AuthzResponse_Error); ok {
return x.Error
}
return nil
}
type isAuthzResponse_Response interface {
isAuthzResponse_Response()
}
type AuthzResponse_Ok_ struct {
Ok *AuthzResponse_Ok `protobuf:"bytes,1,opt,name=ok,proto3,oneof"`
}
type AuthzResponse_Error struct {
Error *ResourceError `protobuf:"bytes,2,opt,name=error,proto3,oneof"`
}
func (*AuthzResponse_Ok_) isAuthzResponse_Response() {}
func (*AuthzResponse_Error) isAuthzResponse_Response() {}
type BasicStats struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
SuccessCount uint64 `protobuf:"varint,1,opt,name=success_count,json=successCount,proto3" json:"success_count,omitempty"`
FailureCount uint64 `protobuf:"varint,2,opt,name=failure_count,json=failureCount,proto3" json:"failure_count,omitempty"`
LatencyMsP50 uint64 `protobuf:"varint,3,opt,name=latency_ms_p50,json=latencyMsP50,proto3" json:"latency_ms_p50,omitempty"`
LatencyMsP95 uint64 `protobuf:"varint,4,opt,name=latency_ms_p95,json=latencyMsP95,proto3" json:"latency_ms_p95,omitempty"`
LatencyMsP99 uint64 `protobuf:"varint,5,opt,name=latency_ms_p99,json=latencyMsP99,proto3" json:"latency_ms_p99,omitempty"`
ActualSuccessCount uint64 `protobuf:"varint,6,opt,name=actual_success_count,json=actualSuccessCount,proto3" json:"actual_success_count,omitempty"`
ActualFailureCount uint64 `protobuf:"varint,7,opt,name=actual_failure_count,json=actualFailureCount,proto3" json:"actual_failure_count,omitempty"`
}
func (x *BasicStats) Reset() {
*x = BasicStats{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[23]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *BasicStats) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*BasicStats) ProtoMessage() {}
func (x *BasicStats) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[23]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use BasicStats.ProtoReflect.Descriptor instead.
func (*BasicStats) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{23}
}
func (x *BasicStats) GetSuccessCount() uint64 {
if x != nil {
return x.SuccessCount
}
return 0
}
func (x *BasicStats) GetFailureCount() uint64 {
if x != nil {
return x.FailureCount
}
return 0
}
func (x *BasicStats) GetLatencyMsP50() uint64 {
if x != nil {
return x.LatencyMsP50
}
return 0
}
func (x *BasicStats) GetLatencyMsP95() uint64 {
if x != nil {
return x.LatencyMsP95
}
return 0
}
func (x *BasicStats) GetLatencyMsP99() uint64 {
if x != nil {
return x.LatencyMsP99
}
return 0
}
func (x *BasicStats) GetActualSuccessCount() uint64 {
if x != nil {
return x.ActualSuccessCount
}
return 0
}
func (x *BasicStats) GetActualFailureCount() uint64 {
if x != nil {
return x.ActualFailureCount
}
return 0
}
type TcpStats struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// number of currently open connections
OpenConnections uint64 `protobuf:"varint,1,opt,name=open_connections,json=openConnections,proto3" json:"open_connections,omitempty"`
// total count of bytes read from peers
ReadBytesTotal uint64 `protobuf:"varint,2,opt,name=read_bytes_total,json=readBytesTotal,proto3" json:"read_bytes_total,omitempty"`
// total count of bytes written to peers
WriteBytesTotal uint64 `protobuf:"varint,3,opt,name=write_bytes_total,json=writeBytesTotal,proto3" json:"write_bytes_total,omitempty"`
}
func (x *TcpStats) Reset() {
*x = TcpStats{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[24]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TcpStats) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TcpStats) ProtoMessage() {}
func (x *TcpStats) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[24]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TcpStats.ProtoReflect.Descriptor instead.
func (*TcpStats) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{24}
}
func (x *TcpStats) GetOpenConnections() uint64 {
if x != nil {
return x.OpenConnections
}
return 0
}
func (x *TcpStats) GetReadBytesTotal() uint64 {
if x != nil {
return x.ReadBytesTotal
}
return 0
}
func (x *TcpStats) GetWriteBytesTotal() uint64 {
if x != nil {
return x.WriteBytesTotal
}
return 0
}
type TrafficSplitStats struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Apex string `protobuf:"bytes,2,opt,name=apex,proto3" json:"apex,omitempty"`
Leaf string `protobuf:"bytes,3,opt,name=leaf,proto3" json:"leaf,omitempty"`
Weight string `protobuf:"bytes,4,opt,name=weight,proto3" json:"weight,omitempty"`
}
func (x *TrafficSplitStats) Reset() {
*x = TrafficSplitStats{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[25]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TrafficSplitStats) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TrafficSplitStats) ProtoMessage() {}
func (x *TrafficSplitStats) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[25]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TrafficSplitStats.ProtoReflect.Descriptor instead.
func (*TrafficSplitStats) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{25}
}
func (x *TrafficSplitStats) GetApex() string {
if x != nil {
return x.Apex
}
return ""
}
func (x *TrafficSplitStats) GetLeaf() string {
if x != nil {
return x.Leaf
}
return ""
}
func (x *TrafficSplitStats) GetWeight() string {
if x != nil {
return x.Weight
}
return ""
}
type ServerStats struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
AllowedCount uint64 `protobuf:"varint,1,opt,name=allowed_count,json=allowedCount,proto3" json:"allowed_count,omitempty"`
DeniedCount uint64 `protobuf:"varint,2,opt,name=denied_count,json=deniedCount,proto3" json:"denied_count,omitempty"`
Srv *Resource `protobuf:"bytes,3,opt,name=srv,proto3" json:"srv,omitempty"`
Route *Resource `protobuf:"bytes,4,opt,name=route,proto3" json:"route,omitempty"`
Authz *Resource `protobuf:"bytes,5,opt,name=authz,proto3" json:"authz,omitempty"`
}
func (x *ServerStats) Reset() {
*x = ServerStats{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[26]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ServerStats) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ServerStats) ProtoMessage() {}
func (x *ServerStats) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[26]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ServerStats.ProtoReflect.Descriptor instead.
func (*ServerStats) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{26}
}
func (x *ServerStats) GetAllowedCount() uint64 {
if x != nil {
return x.AllowedCount
}
return 0
}
func (x *ServerStats) GetDeniedCount() uint64 {
if x != nil {
return x.DeniedCount
}
return 0
}
func (x *ServerStats) GetSrv() *Resource {
if x != nil {
return x.Srv
}
return nil
}
func (x *ServerStats) GetRoute() *Resource {
if x != nil {
return x.Route
}
return nil
}
func (x *ServerStats) GetAuthz() *Resource {
if x != nil {
return x.Authz
}
return nil
}
type StatTable struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Table:
//
// *StatTable_PodGroup_
Table isStatTable_Table `protobuf_oneof:"table"`
}
func (x *StatTable) Reset() {
*x = StatTable{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[27]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *StatTable) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*StatTable) ProtoMessage() {}
func (x *StatTable) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[27]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use StatTable.ProtoReflect.Descriptor instead.
func (*StatTable) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{27}
}
func (m *StatTable) GetTable() isStatTable_Table {
if m != nil {
return m.Table
}
return nil
}
func (x *StatTable) GetPodGroup() *StatTable_PodGroup {
if x, ok := x.GetTable().(*StatTable_PodGroup_); ok {
return x.PodGroup
}
return nil
}
type isStatTable_Table interface {
isStatTable_Table()
}
type StatTable_PodGroup_ struct {
PodGroup *StatTable_PodGroup `protobuf:"bytes,1,opt,name=pod_group,json=podGroup,proto3,oneof"`
}
func (*StatTable_PodGroup_) isStatTable_Table() {}
type EdgesRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Selector *ResourceSelection `protobuf:"bytes,1,opt,name=selector,proto3" json:"selector,omitempty"`
}
func (x *EdgesRequest) Reset() {
*x = EdgesRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[28]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *EdgesRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*EdgesRequest) ProtoMessage() {}
func (x *EdgesRequest) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[28]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use EdgesRequest.ProtoReflect.Descriptor instead.
func (*EdgesRequest) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{28}
}
func (x *EdgesRequest) GetSelector() *ResourceSelection {
if x != nil {
return x.Selector
}
return nil
}
type EdgesResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Response:
//
// *EdgesResponse_Ok_
// *EdgesResponse_Error
Response isEdgesResponse_Response `protobuf_oneof:"response"`
}
func (x *EdgesResponse) Reset() {
*x = EdgesResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[29]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *EdgesResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*EdgesResponse) ProtoMessage() {}
func (x *EdgesResponse) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[29]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use EdgesResponse.ProtoReflect.Descriptor instead.
func (*EdgesResponse) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{29}
}
func (m *EdgesResponse) GetResponse() isEdgesResponse_Response {
if m != nil {
return m.Response
}
return nil
}
func (x *EdgesResponse) GetOk() *EdgesResponse_Ok {
if x, ok := x.GetResponse().(*EdgesResponse_Ok_); ok {
return x.Ok
}
return nil
}
func (x *EdgesResponse) GetError() *ResourceError {
if x, ok := x.GetResponse().(*EdgesResponse_Error); ok {
return x.Error
}
return nil
}
type isEdgesResponse_Response interface {
isEdgesResponse_Response()
}
type EdgesResponse_Ok_ struct {
Ok *EdgesResponse_Ok `protobuf:"bytes,1,opt,name=ok,proto3,oneof"`
}
type EdgesResponse_Error struct {
Error *ResourceError `protobuf:"bytes,2,opt,name=error,proto3,oneof"`
}
func (*EdgesResponse_Ok_) isEdgesResponse_Response() {}
func (*EdgesResponse_Error) isEdgesResponse_Response() {}
type Edge struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Src *Resource `protobuf:"bytes,1,opt,name=src,proto3" json:"src,omitempty"`
Dst *Resource `protobuf:"bytes,2,opt,name=dst,proto3" json:"dst,omitempty"`
ClientId string `protobuf:"bytes,3,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"`
ServerId string `protobuf:"bytes,4,opt,name=server_id,json=serverId,proto3" json:"server_id,omitempty"`
NoIdentityMsg string `protobuf:"bytes,5,opt,name=no_identity_msg,json=noIdentityMsg,proto3" json:"no_identity_msg,omitempty"`
}
func (x *Edge) Reset() {
*x = Edge{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[30]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Edge) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Edge) ProtoMessage() {}
func (x *Edge) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[30]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Edge.ProtoReflect.Descriptor instead.
func (*Edge) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{30}
}
func (x *Edge) GetSrc() *Resource {
if x != nil {
return x.Src
}
return nil
}
func (x *Edge) GetDst() *Resource {
if x != nil {
return x.Dst
}
return nil
}
func (x *Edge) GetClientId() string {
if x != nil {
return x.ClientId
}
return ""
}
func (x *Edge) GetServerId() string {
if x != nil {
return x.ServerId
}
return ""
}
func (x *Edge) GetNoIdentityMsg() string {
if x != nil {
return x.NoIdentityMsg
}
return ""
}
type TopRoutesRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Selector *ResourceSelection `protobuf:"bytes,1,opt,name=selector,proto3" json:"selector,omitempty"`
TimeWindow string `protobuf:"bytes,2,opt,name=time_window,json=timeWindow,proto3" json:"time_window,omitempty"`
// Types that are assignable to Outbound:
//
// *TopRoutesRequest_None
// *TopRoutesRequest_ToResource
Outbound isTopRoutesRequest_Outbound `protobuf_oneof:"outbound"`
}
func (x *TopRoutesRequest) Reset() {
*x = TopRoutesRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[31]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TopRoutesRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TopRoutesRequest) ProtoMessage() {}
func (x *TopRoutesRequest) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[31]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TopRoutesRequest.ProtoReflect.Descriptor instead.
func (*TopRoutesRequest) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{31}
}
func (x *TopRoutesRequest) GetSelector() *ResourceSelection {
if x != nil {
return x.Selector
}
return nil
}
func (x *TopRoutesRequest) GetTimeWindow() string {
if x != nil {
return x.TimeWindow
}
return ""
}
func (m *TopRoutesRequest) GetOutbound() isTopRoutesRequest_Outbound {
if m != nil {
return m.Outbound
}
return nil
}
func (x *TopRoutesRequest) GetNone() *Empty {
if x, ok := x.GetOutbound().(*TopRoutesRequest_None); ok {
return x.None
}
return nil
}
func (x *TopRoutesRequest) GetToResource() *Resource {
if x, ok := x.GetOutbound().(*TopRoutesRequest_ToResource); ok {
return x.ToResource
}
return nil
}
type isTopRoutesRequest_Outbound interface {
isTopRoutesRequest_Outbound()
}
type TopRoutesRequest_None struct {
None *Empty `protobuf:"bytes,3,opt,name=none,proto3,oneof"`
}
type TopRoutesRequest_ToResource struct {
ToResource *Resource `protobuf:"bytes,7,opt,name=to_resource,json=toResource,proto3,oneof"`
}
func (*TopRoutesRequest_None) isTopRoutesRequest_Outbound() {}
func (*TopRoutesRequest_ToResource) isTopRoutesRequest_Outbound() {}
type TopRoutesResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Response:
//
// *TopRoutesResponse_Error
// *TopRoutesResponse_Ok_
Response isTopRoutesResponse_Response `protobuf_oneof:"response"`
}
func (x *TopRoutesResponse) Reset() {
*x = TopRoutesResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[32]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TopRoutesResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TopRoutesResponse) ProtoMessage() {}
func (x *TopRoutesResponse) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[32]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TopRoutesResponse.ProtoReflect.Descriptor instead.
func (*TopRoutesResponse) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{32}
}
func (m *TopRoutesResponse) GetResponse() isTopRoutesResponse_Response {
if m != nil {
return m.Response
}
return nil
}
func (x *TopRoutesResponse) GetError() *ResourceError {
if x, ok := x.GetResponse().(*TopRoutesResponse_Error); ok {
return x.Error
}
return nil
}
func (x *TopRoutesResponse) GetOk() *TopRoutesResponse_Ok {
if x, ok := x.GetResponse().(*TopRoutesResponse_Ok_); ok {
return x.Ok
}
return nil
}
type isTopRoutesResponse_Response interface {
isTopRoutesResponse_Response()
}
type TopRoutesResponse_Error struct {
Error *ResourceError `protobuf:"bytes,2,opt,name=error,proto3,oneof"`
}
type TopRoutesResponse_Ok_ struct {
Ok *TopRoutesResponse_Ok `protobuf:"bytes,3,opt,name=ok,proto3,oneof"`
}
func (*TopRoutesResponse_Error) isTopRoutesResponse_Response() {}
func (*TopRoutesResponse_Ok_) isTopRoutesResponse_Response() {}
type RouteTable struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Rows []*RouteTable_Row `protobuf:"bytes,1,rep,name=rows,proto3" json:"rows,omitempty"`
Resource string `protobuf:"bytes,2,opt,name=resource,proto3" json:"resource,omitempty"`
}
func (x *RouteTable) Reset() {
*x = RouteTable{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[33]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *RouteTable) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RouteTable) ProtoMessage() {}
func (x *RouteTable) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[33]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RouteTable.ProtoReflect.Descriptor instead.
func (*RouteTable) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{33}
}
func (x *RouteTable) GetRows() []*RouteTable_Row {
if x != nil {
return x.Rows
}
return nil
}
func (x *RouteTable) GetResource() string {
if x != nil {
return x.Resource
}
return ""
}
type GatewaysTable struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Rows []*GatewaysTable_Row `protobuf:"bytes,1,rep,name=rows,proto3" json:"rows,omitempty"`
}
func (x *GatewaysTable) Reset() {
*x = GatewaysTable{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[34]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GatewaysTable) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GatewaysTable) ProtoMessage() {}
func (x *GatewaysTable) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[34]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GatewaysTable.ProtoReflect.Descriptor instead.
func (*GatewaysTable) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{34}
}
func (x *GatewaysTable) GetRows() []*GatewaysTable_Row {
if x != nil {
return x.Rows
}
return nil
}
type GatewaysRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
RemoteClusterName string `protobuf:"bytes,1,opt,name=remote_cluster_name,json=remoteClusterName,proto3" json:"remote_cluster_name,omitempty"`
GatewayNamespace string `protobuf:"bytes,2,opt,name=gateway_namespace,json=gatewayNamespace,proto3" json:"gateway_namespace,omitempty"`
TimeWindow string `protobuf:"bytes,3,opt,name=time_window,json=timeWindow,proto3" json:"time_window,omitempty"`
}
func (x *GatewaysRequest) Reset() {
*x = GatewaysRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[35]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GatewaysRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GatewaysRequest) ProtoMessage() {}
func (x *GatewaysRequest) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[35]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GatewaysRequest.ProtoReflect.Descriptor instead.
func (*GatewaysRequest) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{35}
}
func (x *GatewaysRequest) GetRemoteClusterName() string {
if x != nil {
return x.RemoteClusterName
}
return ""
}
func (x *GatewaysRequest) GetGatewayNamespace() string {
if x != nil {
return x.GatewayNamespace
}
return ""
}
func (x *GatewaysRequest) GetTimeWindow() string {
if x != nil {
return x.TimeWindow
}
return ""
}
type GatewaysResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Response:
//
// *GatewaysResponse_Ok_
// *GatewaysResponse_Error
Response isGatewaysResponse_Response `protobuf_oneof:"response"`
}
func (x *GatewaysResponse) Reset() {
*x = GatewaysResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[36]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GatewaysResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GatewaysResponse) ProtoMessage() {}
func (x *GatewaysResponse) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[36]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GatewaysResponse.ProtoReflect.Descriptor instead.
func (*GatewaysResponse) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{36}
}
func (m *GatewaysResponse) GetResponse() isGatewaysResponse_Response {
if m != nil {
return m.Response
}
return nil
}
func (x *GatewaysResponse) GetOk() *GatewaysResponse_Ok {
if x, ok := x.GetResponse().(*GatewaysResponse_Ok_); ok {
return x.Ok
}
return nil
}
func (x *GatewaysResponse) GetError() *ResourceError {
if x, ok := x.GetResponse().(*GatewaysResponse_Error); ok {
return x.Error
}
return nil
}
type isGatewaysResponse_Response interface {
isGatewaysResponse_Response()
}
type GatewaysResponse_Ok_ struct {
Ok *GatewaysResponse_Ok `protobuf:"bytes,1,opt,name=ok,proto3,oneof"`
}
type GatewaysResponse_Error struct {
Error *ResourceError `protobuf:"bytes,2,opt,name=error,proto3,oneof"`
}
func (*GatewaysResponse_Ok_) isGatewaysResponse_Response() {}
func (*GatewaysResponse_Error) isGatewaysResponse_Response() {}
type Headers_Header struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The name of a header in a request.
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
// The value of a header in a request. If the value consists entirely of
// UTF-8 encodings, `value` will be set; otherwise a binary value is
// assumed and `value_bin` will be set.
//
// Types that are assignable to Value:
//
// *Headers_Header_ValueStr
// *Headers_Header_ValueBin
Value isHeaders_Header_Value `protobuf_oneof:"value"`
}
func (x *Headers_Header) Reset() {
*x = Headers_Header{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[37]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Headers_Header) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Headers_Header) ProtoMessage() {}
func (x *Headers_Header) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[37]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Headers_Header.ProtoReflect.Descriptor instead.
func (*Headers_Header) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{12, 0}
}
func (x *Headers_Header) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (m *Headers_Header) GetValue() isHeaders_Header_Value {
if m != nil {
return m.Value
}
return nil
}
func (x *Headers_Header) GetValueStr() string {
if x, ok := x.GetValue().(*Headers_Header_ValueStr); ok {
return x.ValueStr
}
return ""
}
func (x *Headers_Header) GetValueBin() []byte {
if x, ok := x.GetValue().(*Headers_Header_ValueBin); ok {
return x.ValueBin
}
return nil
}
type isHeaders_Header_Value interface {
isHeaders_Header_Value()
}
type Headers_Header_ValueStr struct {
ValueStr string `protobuf:"bytes,2,opt,name=value_str,json=valueStr,proto3,oneof"`
}
type Headers_Header_ValueBin struct {
ValueBin []byte `protobuf:"bytes,3,opt,name=value_bin,json=valueBin,proto3,oneof"`
}
func (*Headers_Header_ValueStr) isHeaders_Header_Value() {}
func (*Headers_Header_ValueBin) isHeaders_Header_Value() {}
type PodErrors_PodError struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Error:
//
// *PodErrors_PodError_Container
Error isPodErrors_PodError_Error `protobuf_oneof:"error"`
}
func (x *PodErrors_PodError) Reset() {
*x = PodErrors_PodError{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[38]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PodErrors_PodError) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PodErrors_PodError) ProtoMessage() {}
func (x *PodErrors_PodError) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[38]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PodErrors_PodError.ProtoReflect.Descriptor instead.
func (*PodErrors_PodError) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{15, 0}
}
func (m *PodErrors_PodError) GetError() isPodErrors_PodError_Error {
if m != nil {
return m.Error
}
return nil
}
func (x *PodErrors_PodError) GetContainer() *PodErrors_PodError_ContainerError {
if x, ok := x.GetError().(*PodErrors_PodError_Container); ok {
return x.Container
}
return nil
}
type isPodErrors_PodError_Error interface {
isPodErrors_PodError_Error()
}
type PodErrors_PodError_Container struct {
Container *PodErrors_PodError_ContainerError `protobuf:"bytes,1,opt,name=container,proto3,oneof"`
}
func (*PodErrors_PodError_Container) isPodErrors_PodError_Error() {}
// To report init-container and container failures
type PodErrors_PodError_ContainerError struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
Container string `protobuf:"bytes,2,opt,name=container,proto3" json:"container,omitempty"`
Image string `protobuf:"bytes,3,opt,name=image,proto3" json:"image,omitempty"`
Reason string `protobuf:"bytes,4,opt,name=reason,proto3" json:"reason,omitempty"`
}
func (x *PodErrors_PodError_ContainerError) Reset() {
*x = PodErrors_PodError_ContainerError{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[39]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PodErrors_PodError_ContainerError) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PodErrors_PodError_ContainerError) ProtoMessage() {}
func (x *PodErrors_PodError_ContainerError) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[39]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PodErrors_PodError_ContainerError.ProtoReflect.Descriptor instead.
func (*PodErrors_PodError_ContainerError) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{15, 0, 0}
}
func (x *PodErrors_PodError_ContainerError) GetMessage() string {
if x != nil {
return x.Message
}
return ""
}
func (x *PodErrors_PodError_ContainerError) GetContainer() string {
if x != nil {
return x.Container
}
return ""
}
func (x *PodErrors_PodError_ContainerError) GetImage() string {
if x != nil {
return x.Image
}
return ""
}
func (x *PodErrors_PodError_ContainerError) GetReason() string {
if x != nil {
return x.Reason
}
return ""
}
type StatSummaryResponse_Ok struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
StatTables []*StatTable `protobuf:"bytes,1,rep,name=stat_tables,json=statTables,proto3" json:"stat_tables,omitempty"`
}
func (x *StatSummaryResponse_Ok) Reset() {
*x = StatSummaryResponse_Ok{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[40]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *StatSummaryResponse_Ok) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*StatSummaryResponse_Ok) ProtoMessage() {}
func (x *StatSummaryResponse_Ok) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[40]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use StatSummaryResponse_Ok.ProtoReflect.Descriptor instead.
func (*StatSummaryResponse_Ok) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{20, 0}
}
func (x *StatSummaryResponse_Ok) GetStatTables() []*StatTable {
if x != nil {
return x.StatTables
}
return nil
}
type AuthzResponse_Ok struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
StatTable *StatTable `protobuf:"bytes,1,opt,name=stat_table,json=statTable,proto3" json:"stat_table,omitempty"`
}
func (x *AuthzResponse_Ok) Reset() {
*x = AuthzResponse_Ok{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[41]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AuthzResponse_Ok) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AuthzResponse_Ok) ProtoMessage() {}
func (x *AuthzResponse_Ok) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[41]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AuthzResponse_Ok.ProtoReflect.Descriptor instead.
func (*AuthzResponse_Ok) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{22, 0}
}
func (x *AuthzResponse_Ok) GetStatTable() *StatTable {
if x != nil {
return x.StatTable
}
return nil
}
type StatTable_PodGroup struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Rows []*StatTable_PodGroup_Row `protobuf:"bytes,1,rep,name=rows,proto3" json:"rows,omitempty"`
}
func (x *StatTable_PodGroup) Reset() {
*x = StatTable_PodGroup{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[42]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *StatTable_PodGroup) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*StatTable_PodGroup) ProtoMessage() {}
func (x *StatTable_PodGroup) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[42]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use StatTable_PodGroup.ProtoReflect.Descriptor instead.
func (*StatTable_PodGroup) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{27, 0}
}
func (x *StatTable_PodGroup) GetRows() []*StatTable_PodGroup_Row {
if x != nil {
return x.Rows
}
return nil
}
type StatTable_PodGroup_Row struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Resource *Resource `protobuf:"bytes,1,opt,name=resource,proto3" json:"resource,omitempty"`
TimeWindow string `protobuf:"bytes,2,opt,name=time_window,json=timeWindow,proto3" json:"time_window,omitempty"`
// pod status on Kubernetes
Status string `protobuf:"bytes,9,opt,name=status,proto3" json:"status,omitempty"`
// number of pending or running pods in this resource that have linkerd injected
MeshedPodCount uint64 `protobuf:"varint,3,opt,name=meshed_pod_count,json=meshedPodCount,proto3" json:"meshed_pod_count,omitempty"`
// number of pending or running pods in this resource
RunningPodCount uint64 `protobuf:"varint,4,opt,name=running_pod_count,json=runningPodCount,proto3" json:"running_pod_count,omitempty"`
// number of pods in this resource that have Phase PodFailed
FailedPodCount uint64 `protobuf:"varint,6,opt,name=failed_pod_count,json=failedPodCount,proto3" json:"failed_pod_count,omitempty"`
Stats *BasicStats `protobuf:"bytes,5,opt,name=stats,proto3" json:"stats,omitempty"`
TcpStats *TcpStats `protobuf:"bytes,8,opt,name=tcp_stats,json=tcpStats,proto3" json:"tcp_stats,omitempty"`
TsStats *TrafficSplitStats `protobuf:"bytes,10,opt,name=ts_stats,json=tsStats,proto3" json:"ts_stats,omitempty"`
SrvStats *ServerStats `protobuf:"bytes,11,opt,name=srv_stats,json=srvStats,proto3" json:"srv_stats,omitempty"`
// Stores a set of errors for each pod name. If a pod has no errors, it may be omitted.
ErrorsByPod map[string]*PodErrors `protobuf:"bytes,7,rep,name=errors_by_pod,json=errorsByPod,proto3" json:"errors_by_pod,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
}
func (x *StatTable_PodGroup_Row) Reset() {
*x = StatTable_PodGroup_Row{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[43]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *StatTable_PodGroup_Row) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*StatTable_PodGroup_Row) ProtoMessage() {}
func (x *StatTable_PodGroup_Row) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[43]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use StatTable_PodGroup_Row.ProtoReflect.Descriptor instead.
func (*StatTable_PodGroup_Row) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{27, 0, 0}
}
func (x *StatTable_PodGroup_Row) GetResource() *Resource {
if x != nil {
return x.Resource
}
return nil
}
func (x *StatTable_PodGroup_Row) GetTimeWindow() string {
if x != nil {
return x.TimeWindow
}
return ""
}
func (x *StatTable_PodGroup_Row) GetStatus() string {
if x != nil {
return x.Status
}
return ""
}
func (x *StatTable_PodGroup_Row) GetMeshedPodCount() uint64 {
if x != nil {
return x.MeshedPodCount
}
return 0
}
func (x *StatTable_PodGroup_Row) GetRunningPodCount() uint64 {
if x != nil {
return x.RunningPodCount
}
return 0
}
func (x *StatTable_PodGroup_Row) GetFailedPodCount() uint64 {
if x != nil {
return x.FailedPodCount
}
return 0
}
func (x *StatTable_PodGroup_Row) GetStats() *BasicStats {
if x != nil {
return x.Stats
}
return nil
}
func (x *StatTable_PodGroup_Row) GetTcpStats() *TcpStats {
if x != nil {
return x.TcpStats
}
return nil
}
func (x *StatTable_PodGroup_Row) GetTsStats() *TrafficSplitStats {
if x != nil {
return x.TsStats
}
return nil
}
func (x *StatTable_PodGroup_Row) GetSrvStats() *ServerStats {
if x != nil {
return x.SrvStats
}
return nil
}
func (x *StatTable_PodGroup_Row) GetErrorsByPod() map[string]*PodErrors {
if x != nil {
return x.ErrorsByPod
}
return nil
}
type EdgesResponse_Ok struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Edges []*Edge `protobuf:"bytes,1,rep,name=edges,proto3" json:"edges,omitempty"`
}
func (x *EdgesResponse_Ok) Reset() {
*x = EdgesResponse_Ok{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[45]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *EdgesResponse_Ok) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*EdgesResponse_Ok) ProtoMessage() {}
func (x *EdgesResponse_Ok) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[45]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use EdgesResponse_Ok.ProtoReflect.Descriptor instead.
func (*EdgesResponse_Ok) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{29, 0}
}
func (x *EdgesResponse_Ok) GetEdges() []*Edge {
if x != nil {
return x.Edges
}
return nil
}
type TopRoutesResponse_Ok struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Routes []*RouteTable `protobuf:"bytes,1,rep,name=routes,proto3" json:"routes,omitempty"`
}
func (x *TopRoutesResponse_Ok) Reset() {
*x = TopRoutesResponse_Ok{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[46]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TopRoutesResponse_Ok) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TopRoutesResponse_Ok) ProtoMessage() {}
func (x *TopRoutesResponse_Ok) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[46]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TopRoutesResponse_Ok.ProtoReflect.Descriptor instead.
func (*TopRoutesResponse_Ok) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{32, 0}
}
func (x *TopRoutesResponse_Ok) GetRoutes() []*RouteTable {
if x != nil {
return x.Routes
}
return nil
}
type RouteTable_Row struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Route string `protobuf:"bytes,1,opt,name=route,proto3" json:"route,omitempty"`
TimeWindow string `protobuf:"bytes,2,opt,name=time_window,json=timeWindow,proto3" json:"time_window,omitempty"`
Authority string `protobuf:"bytes,6,opt,name=authority,proto3" json:"authority,omitempty"`
Stats *BasicStats `protobuf:"bytes,5,opt,name=stats,proto3" json:"stats,omitempty"`
}
func (x *RouteTable_Row) Reset() {
*x = RouteTable_Row{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[47]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *RouteTable_Row) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RouteTable_Row) ProtoMessage() {}
func (x *RouteTable_Row) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[47]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RouteTable_Row.ProtoReflect.Descriptor instead.
func (*RouteTable_Row) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{33, 0}
}
func (x *RouteTable_Row) GetRoute() string {
if x != nil {
return x.Route
}
return ""
}
func (x *RouteTable_Row) GetTimeWindow() string {
if x != nil {
return x.TimeWindow
}
return ""
}
func (x *RouteTable_Row) GetAuthority() string {
if x != nil {
return x.Authority
}
return ""
}
func (x *RouteTable_Row) GetStats() *BasicStats {
if x != nil {
return x.Stats
}
return nil
}
type GatewaysTable_Row struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Namespace string `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
ClusterName string `protobuf:"bytes,3,opt,name=cluster_name,json=clusterName,proto3" json:"cluster_name,omitempty"`
PairedServices uint64 `protobuf:"varint,4,opt,name=paired_services,json=pairedServices,proto3" json:"paired_services,omitempty"`
Alive bool `protobuf:"varint,5,opt,name=alive,proto3" json:"alive,omitempty"`
LatencyMsP50 uint64 `protobuf:"varint,6,opt,name=latency_ms_p50,json=latencyMsP50,proto3" json:"latency_ms_p50,omitempty"`
LatencyMsP95 uint64 `protobuf:"varint,7,opt,name=latency_ms_p95,json=latencyMsP95,proto3" json:"latency_ms_p95,omitempty"`
LatencyMsP99 uint64 `protobuf:"varint,8,opt,name=latency_ms_p99,json=latencyMsP99,proto3" json:"latency_ms_p99,omitempty"`
}
func (x *GatewaysTable_Row) Reset() {
*x = GatewaysTable_Row{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[48]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GatewaysTable_Row) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GatewaysTable_Row) ProtoMessage() {}
func (x *GatewaysTable_Row) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[48]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GatewaysTable_Row.ProtoReflect.Descriptor instead.
func (*GatewaysTable_Row) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{34, 0}
}
func (x *GatewaysTable_Row) GetNamespace() string {
if x != nil {
return x.Namespace
}
return ""
}
func (x *GatewaysTable_Row) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *GatewaysTable_Row) GetClusterName() string {
if x != nil {
return x.ClusterName
}
return ""
}
func (x *GatewaysTable_Row) GetPairedServices() uint64 {
if x != nil {
return x.PairedServices
}
return 0
}
func (x *GatewaysTable_Row) GetAlive() bool {
if x != nil {
return x.Alive
}
return false
}
func (x *GatewaysTable_Row) GetLatencyMsP50() uint64 {
if x != nil {
return x.LatencyMsP50
}
return 0
}
func (x *GatewaysTable_Row) GetLatencyMsP95() uint64 {
if x != nil {
return x.LatencyMsP95
}
return 0
}
func (x *GatewaysTable_Row) GetLatencyMsP99() uint64 {
if x != nil {
return x.LatencyMsP99
}
return 0
}
type GatewaysResponse_Ok struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
GatewaysTable *GatewaysTable `protobuf:"bytes,1,opt,name=gateways_table,json=gatewaysTable,proto3" json:"gateways_table,omitempty"`
}
func (x *GatewaysResponse_Ok) Reset() {
*x = GatewaysResponse_Ok{}
if protoimpl.UnsafeEnabled {
mi := &file_viz_proto_msgTypes[49]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GatewaysResponse_Ok) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GatewaysResponse_Ok) ProtoMessage() {}
func (x *GatewaysResponse_Ok) ProtoReflect() protoreflect.Message {
mi := &file_viz_proto_msgTypes[49]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GatewaysResponse_Ok.ProtoReflect.Descriptor instead.
func (*GatewaysResponse_Ok) Descriptor() ([]byte, []int) {
return file_viz_proto_rawDescGZIP(), []int{36, 0}
}
func (x *GatewaysResponse_Ok) GetGatewaysTable() *GatewaysTable {
if x != nil {
return x.GatewaysTable
}
return nil
}
var File_viz_proto protoreflect.FileDescriptor
var file_viz_proto_rawDesc = []byte{
0x0a, 0x09, 0x76, 0x69, 0x7a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x6c, 0x69, 0x6e,
0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70,
0x74, 0x79, 0x22, 0xc8, 0x01, 0x0a, 0x0b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x75,
0x6c, 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x53, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x4e,
0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x53, 0x75, 0x62, 0x73, 0x79,
0x73, 0x74, 0x65, 0x6d, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2a, 0x0a, 0x10, 0x43, 0x68, 0x65, 0x63,
0x6b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x52, 0x10, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70,
0x74, 0x69, 0x6f, 0x6e, 0x12, 0x31, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03,
0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e,
0x76, 0x69, 0x7a, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52,
0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x34, 0x0a, 0x15, 0x46, 0x72, 0x69, 0x65, 0x6e,
0x64, 0x6c, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x55, 0x73, 0x65, 0x72,
0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x6c, 0x79,
0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x55, 0x73, 0x65, 0x72, 0x22, 0x12, 0x0a,
0x10, 0x53, 0x65, 0x6c, 0x66, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x22, 0x48, 0x0a, 0x11, 0x53, 0x65, 0x6c, 0x66, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74,
0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72,
0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x75,
0x6c, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x33, 0x0a, 0x13, 0x4c,
0x69, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65,
0x22, 0x49, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, 0x08, 0x73, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6c, 0x69, 0x6e,
0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
0x65, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x22, 0x3b, 0x0a, 0x07, 0x53,
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61,
0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e,
0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x4e, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74,
0x50, 0x6f, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3b, 0x0a, 0x08, 0x73,
0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e,
0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73,
0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08,
0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x39, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74,
0x50, 0x6f, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x04,
0x70, 0x6f, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x69, 0x6e,
0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x50, 0x6f, 0x64, 0x52, 0x04, 0x70,
0x6f, 0x64, 0x73, 0x22, 0xfa, 0x04, 0x0a, 0x03, 0x50, 0x6f, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e,
0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12,
0x14, 0x0a, 0x05, 0x70, 0x6f, 0x64, 0x49, 0x50, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
0x70, 0x6f, 0x64, 0x49, 0x50, 0x12, 0x20, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d,
0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, 0x64, 0x65, 0x70,
0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0b, 0x72, 0x65, 0x70, 0x6c, 0x69,
0x63, 0x61, 0x5f, 0x73, 0x65, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a,
0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x53, 0x65, 0x74, 0x12, 0x37, 0x0a, 0x16, 0x72, 0x65,
0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
0x6c, 0x6c, 0x65, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x15, 0x72, 0x65,
0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
0x6c, 0x65, 0x72, 0x12, 0x23, 0x0a, 0x0c, 0x73, 0x74, 0x61, 0x74, 0x65, 0x66, 0x75, 0x6c, 0x5f,
0x73, 0x65, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x73, 0x74, 0x61,
0x74, 0x65, 0x66, 0x75, 0x6c, 0x53, 0x65, 0x74, 0x12, 0x1f, 0x0a, 0x0a, 0x64, 0x61, 0x65, 0x6d,
0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x09,
0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x12, 0x12, 0x0a, 0x03, 0x6a, 0x6f, 0x62,
0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x03, 0x6a, 0x6f, 0x62, 0x12, 0x16, 0x0a,
0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73,
0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x64, 0x64, 0x65, 0x64, 0x18, 0x05,
0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x61, 0x64, 0x64, 0x65, 0x64, 0x12, 0x43, 0x0a, 0x0f, 0x73,
0x69, 0x6e, 0x63, 0x65, 0x4c, 0x61, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x06,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52,
0x0f, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x4c, 0x61, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74,
0x12, 0x30, 0x0a, 0x13, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x4e, 0x61,
0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x63,
0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61,
0x63, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x50, 0x6c, 0x61,
0x6e, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
0x6c, 0x50, 0x6c, 0x61, 0x6e, 0x65, 0x12, 0x31, 0x0a, 0x06, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65,
0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x52, 0x06, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x72, 0x6f,
0x78, 0x79, 0x52, 0x65, 0x61, 0x64, 0x79, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x70,
0x72, 0x6f, 0x78, 0x79, 0x52, 0x65, 0x61, 0x64, 0x79, 0x12, 0x22, 0x0a, 0x0c, 0x70, 0x72, 0x6f,
0x78, 0x79, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52,
0x0c, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a,
0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x07, 0x0a, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72,
0x22, 0xf1, 0x01, 0x0a, 0x0a, 0x48, 0x74, 0x74, 0x70, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12,
0x45, 0x0a, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76,
0x69, 0x7a, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x2e, 0x52, 0x65,
0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0a, 0x72, 0x65, 0x67, 0x69,
0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x12, 0x24, 0x0a, 0x0c, 0x75, 0x6e, 0x72, 0x65, 0x67, 0x69,
0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0c,
0x75, 0x6e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x22, 0x6e, 0x0a, 0x0a,
0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x12, 0x07, 0x0a, 0x03, 0x47, 0x45,
0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x50, 0x4f, 0x53, 0x54, 0x10, 0x01, 0x12, 0x07, 0x0a,
0x03, 0x50, 0x55, 0x54, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45,
0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x50, 0x41, 0x54, 0x43, 0x48, 0x10, 0x04, 0x12, 0x0b, 0x0a,
0x07, 0x4f, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x53, 0x10, 0x05, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x4f,
0x4e, 0x4e, 0x45, 0x43, 0x54, 0x10, 0x06, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x45, 0x41, 0x44, 0x10,
0x07, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x08, 0x42, 0x06, 0x0a, 0x04,
0x74, 0x79, 0x70, 0x65, 0x22, 0x9c, 0x01, 0x0a, 0x06, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x12,
0x41, 0x0a, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76,
0x69, 0x7a, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74,
0x65, 0x72, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72,
0x65, 0x64, 0x12, 0x24, 0x0a, 0x0c, 0x75, 0x6e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72,
0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0c, 0x75, 0x6e, 0x72, 0x65,
0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x22, 0x21, 0x0a, 0x0a, 0x52, 0x65, 0x67, 0x69,
0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x10, 0x00,
0x12, 0x09, 0x0a, 0x05, 0x48, 0x54, 0x54, 0x50, 0x53, 0x10, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x74,
0x79, 0x70, 0x65, 0x22, 0xa6, 0x01, 0x0a, 0x07, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12,
0x36, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x1c, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e,
0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x07,
0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x1a, 0x63, 0x0a, 0x06, 0x48, 0x65, 0x61, 0x64, 0x65,
0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x09, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x73,
0x74, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x76, 0x61, 0x6c, 0x75,
0x65, 0x53, 0x74, 0x72, 0x12, 0x1d, 0x0a, 0x09, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x62, 0x69,
0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x08, 0x76, 0x61, 0x6c, 0x75, 0x65,
0x42, 0x69, 0x6e, 0x42, 0x07, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x64, 0x0a, 0x03,
0x45, 0x6f, 0x73, 0x12, 0x2a, 0x0a, 0x10, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x73, 0x74, 0x61, 0x74,
0x75, 0x73, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x00, 0x52,
0x0e, 0x67, 0x72, 0x70, 0x63, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12,
0x2a, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x65, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x63,
0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x00, 0x52, 0x0e, 0x72, 0x65, 0x73,
0x65, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x42, 0x05, 0x0a, 0x03, 0x65,
0x6e, 0x64, 0x22, 0x20, 0x0a, 0x08, 0x41, 0x70, 0x69, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x14,
0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65,
0x72, 0x72, 0x6f, 0x72, 0x22, 0xa4, 0x02, 0x0a, 0x09, 0x50, 0x6f, 0x64, 0x45, 0x72, 0x72, 0x6f,
0x72, 0x73, 0x12, 0x38, 0x0a, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x20, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69,
0x7a, 0x2e, 0x50, 0x6f, 0x64, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x50, 0x6f, 0x64, 0x45,
0x72, 0x72, 0x6f, 0x72, 0x52, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x1a, 0xdc, 0x01, 0x0a,
0x08, 0x50, 0x6f, 0x64, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x4f, 0x0a, 0x09, 0x63, 0x6f, 0x6e,
0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x6c,
0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x50, 0x6f, 0x64, 0x45,
0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x50, 0x6f, 0x64, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x43,
0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x48, 0x00, 0x52,
0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x1a, 0x76, 0x0a, 0x0e, 0x43, 0x6f,
0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x18, 0x0a, 0x07,
0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d,
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69,
0x6e, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61,
0x69, 0x6e, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20,
0x01, 0x28, 0x09, 0x52, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65,
0x61, 0x73, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73,
0x6f, 0x6e, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x50, 0x0a, 0x08, 0x52,
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73,
0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65,
0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,
0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x6e, 0x0a,
0x11, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69,
0x6f, 0x6e, 0x12, 0x32, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e,
0x76, 0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x08, 0x72, 0x65,
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x5f,
0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d,
0x6c, 0x61, 0x62, 0x65, 0x6c, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x59, 0x0a,
0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x32,
0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x16, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e,
0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72,
0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28,
0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0xdf, 0x02, 0x0a, 0x12, 0x53, 0x74, 0x61,
0x74, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
0x3b, 0x0a, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x1f, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a,
0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69,
0x6f, 0x6e, 0x52, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1f, 0x0a, 0x0b,
0x74, 0x69, 0x6d, 0x65, 0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x12, 0x29, 0x0a,
0x04, 0x6e, 0x6f, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x69,
0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
0x48, 0x00, 0x52, 0x04, 0x6e, 0x6f, 0x6e, 0x65, 0x12, 0x39, 0x0a, 0x0b, 0x74, 0x6f, 0x5f, 0x72,
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e,
0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73,
0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, 0x00, 0x52, 0x0a, 0x74, 0x6f, 0x52, 0x65, 0x73, 0x6f, 0x75,
0x72, 0x63, 0x65, 0x12, 0x3d, 0x0a, 0x0d, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x69, 0x6e,
0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72,
0x63, 0x65, 0x48, 0x00, 0x52, 0x0c, 0x66, 0x72, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72,
0x63, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73,
0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x6b, 0x69, 0x70, 0x53, 0x74, 0x61, 0x74,
0x73, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x63, 0x70, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x07,
0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x74, 0x63, 0x70, 0x53, 0x74, 0x61, 0x74, 0x73, 0x42, 0x0a,
0x0a, 0x08, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0xce, 0x01, 0x0a, 0x13, 0x53,
0x74, 0x61, 0x74, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x36, 0x0a, 0x02, 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24,
0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x53, 0x74,
0x61, 0x74, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x2e, 0x4f, 0x6b, 0x48, 0x00, 0x52, 0x02, 0x6f, 0x6b, 0x12, 0x33, 0x0a, 0x05, 0x65, 0x72,
0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6c, 0x69, 0x6e, 0x6b,
0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,
0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x1a,
0x3e, 0x0a, 0x02, 0x4f, 0x6b, 0x12, 0x38, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x5f, 0x74, 0x61,
0x62, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6c, 0x69, 0x6e,
0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x54, 0x61,
0x62, 0x6c, 0x65, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x42,
0x0a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x63, 0x0a, 0x0c, 0x41,
0x75, 0x74, 0x68, 0x7a, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x08, 0x72,
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e,
0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73,
0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12,
0x1f, 0x0a, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77,
0x22, 0xc0, 0x01, 0x0a, 0x0d, 0x41, 0x75, 0x74, 0x68, 0x7a, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x30, 0x0a, 0x02, 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e,
0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x41, 0x75,
0x74, 0x68, 0x7a, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4f, 0x6b, 0x48, 0x00,
0x52, 0x02, 0x6f, 0x6b, 0x12, 0x33, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76,
0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72,
0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x1a, 0x3c, 0x0a, 0x02, 0x4f, 0x6b, 0x12,
0x36, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x5f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76,
0x69, 0x7a, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x09, 0x73, 0x74,
0x61, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x22, 0xac, 0x02, 0x0a, 0x0a, 0x42, 0x61, 0x73, 0x69, 0x63, 0x53, 0x74, 0x61,
0x74, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x63, 0x6f,
0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x73, 0x75, 0x63, 0x63, 0x65,
0x73, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x75,
0x72, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c,
0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0e,
0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6d, 0x73, 0x5f, 0x70, 0x35, 0x30, 0x18, 0x03,
0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x73, 0x50,
0x35, 0x30, 0x12, 0x24, 0x0a, 0x0e, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6d, 0x73,
0x5f, 0x70, 0x39, 0x35, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x6c, 0x61, 0x74, 0x65,
0x6e, 0x63, 0x79, 0x4d, 0x73, 0x50, 0x39, 0x35, 0x12, 0x24, 0x0a, 0x0e, 0x6c, 0x61, 0x74, 0x65,
0x6e, 0x63, 0x79, 0x5f, 0x6d, 0x73, 0x5f, 0x70, 0x39, 0x39, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04,
0x52, 0x0c, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x73, 0x50, 0x39, 0x39, 0x12, 0x30,
0x0a, 0x14, 0x61, 0x63, 0x74, 0x75, 0x61, 0x6c, 0x5f, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73,
0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x12, 0x61, 0x63,
0x74, 0x75, 0x61, 0x6c, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74,
0x12, 0x30, 0x0a, 0x14, 0x61, 0x63, 0x74, 0x75, 0x61, 0x6c, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x75,
0x72, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x12,
0x61, 0x63, 0x74, 0x75, 0x61, 0x6c, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x75,
0x6e, 0x74, 0x22, 0x8b, 0x01, 0x0a, 0x08, 0x54, 0x63, 0x70, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12,
0x29, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69,
0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6f, 0x70, 0x65, 0x6e, 0x43,
0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x65,
0x61, 0x64, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02,
0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x72, 0x65, 0x61, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x54,
0x6f, 0x74, 0x61, 0x6c, 0x12, 0x2a, 0x0a, 0x11, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x62, 0x79,
0x74, 0x65, 0x73, 0x5f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52,
0x0f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x42, 0x79, 0x74, 0x65, 0x73, 0x54, 0x6f, 0x74, 0x61, 0x6c,
0x22, 0x53, 0x0a, 0x11, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x53, 0x70, 0x6c, 0x69, 0x74,
0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x70, 0x65, 0x78, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x70, 0x65, 0x78, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x65, 0x61,
0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x65, 0x61, 0x66, 0x12, 0x16, 0x0a,
0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x77,
0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0xdb, 0x01, 0x0a, 0x0b, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64,
0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x61, 0x6c,
0x6c, 0x6f, 0x77, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x65,
0x6e, 0x69, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04,
0x52, 0x0b, 0x64, 0x65, 0x6e, 0x69, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a,
0x03, 0x73, 0x72, 0x76, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x69, 0x6e,
0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72,
0x63, 0x65, 0x52, 0x03, 0x73, 0x72, 0x76, 0x12, 0x2c, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65,
0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64,
0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x05,
0x72, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x2c, 0x0a, 0x05, 0x61, 0x75, 0x74, 0x68, 0x7a, 0x18, 0x05,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e,
0x76, 0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x05, 0x61, 0x75,
0x74, 0x68, 0x7a, 0x22, 0x9e, 0x06, 0x0a, 0x09, 0x53, 0x74, 0x61, 0x74, 0x54, 0x61, 0x62, 0x6c,
0x65, 0x12, 0x3f, 0x0a, 0x09, 0x70, 0x6f, 0x64, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e,
0x76, 0x69, 0x7a, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x2e, 0x50, 0x6f,
0x64, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x48, 0x00, 0x52, 0x08, 0x70, 0x6f, 0x64, 0x47, 0x72, 0x6f,
0x75, 0x70, 0x1a, 0xc6, 0x05, 0x0a, 0x08, 0x50, 0x6f, 0x64, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12,
0x38, 0x0a, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e,
0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x53, 0x74, 0x61,
0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x2e, 0x50, 0x6f, 0x64, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x2e,
0x52, 0x6f, 0x77, 0x52, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x1a, 0xff, 0x04, 0x0a, 0x03, 0x52, 0x6f,
0x77, 0x12, 0x32, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76,
0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73,
0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x77, 0x69,
0x6e, 0x64, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x69, 0x6d, 0x65,
0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x28,
0x0a, 0x10, 0x6d, 0x65, 0x73, 0x68, 0x65, 0x64, 0x5f, 0x70, 0x6f, 0x64, 0x5f, 0x63, 0x6f, 0x75,
0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x6d, 0x65, 0x73, 0x68, 0x65, 0x64,
0x50, 0x6f, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x72, 0x75, 0x6e, 0x6e,
0x69, 0x6e, 0x67, 0x5f, 0x70, 0x6f, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20,
0x01, 0x28, 0x04, 0x52, 0x0f, 0x72, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x64, 0x43,
0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x5f, 0x70,
0x6f, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e,
0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x50, 0x6f, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2e,
0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e,
0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x42, 0x61, 0x73,
0x69, 0x63, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x12, 0x33,
0x0a, 0x09, 0x74, 0x63, 0x70, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a,
0x2e, 0x54, 0x63, 0x70, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x08, 0x74, 0x63, 0x70, 0x53, 0x74,
0x61, 0x74, 0x73, 0x12, 0x3a, 0x0a, 0x08, 0x74, 0x73, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18,
0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32,
0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x53, 0x70, 0x6c, 0x69,
0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x07, 0x74, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12,
0x36, 0x0a, 0x09, 0x73, 0x72, 0x76, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x0b, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69,
0x7a, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x08, 0x73,
0x72, 0x76, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x59, 0x0a, 0x0d, 0x65, 0x72, 0x72, 0x6f, 0x72,
0x73, 0x5f, 0x62, 0x79, 0x5f, 0x70, 0x6f, 0x64, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35,
0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x53, 0x74,
0x61, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x2e, 0x50, 0x6f, 0x64, 0x47, 0x72, 0x6f, 0x75, 0x70,
0x2e, 0x52, 0x6f, 0x77, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x42, 0x79, 0x50, 0x6f, 0x64,
0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0b, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x42, 0x79, 0x50,
0x6f, 0x64, 0x1a, 0x57, 0x0a, 0x10, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x42, 0x79, 0x50, 0x6f,
0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2d, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72,
0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x50, 0x6f, 0x64, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73,
0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x74,
0x61, 0x62, 0x6c, 0x65, 0x22, 0x4b, 0x0a, 0x0c, 0x45, 0x64, 0x67, 0x65, 0x73, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x12, 0x3b, 0x0a, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64,
0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65,
0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f,
0x72, 0x22, 0xb2, 0x01, 0x0a, 0x0d, 0x45, 0x64, 0x67, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x02, 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x1e, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x45,
0x64, 0x67, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4f, 0x6b, 0x48,
0x00, 0x52, 0x02, 0x6f, 0x6b, 0x12, 0x33, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e,
0x76, 0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x45, 0x72, 0x72, 0x6f,
0x72, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x1a, 0x2e, 0x0a, 0x02, 0x4f, 0x6b,
0x12, 0x28, 0x0a, 0x05, 0x65, 0x64, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x12, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x45,
0x64, 0x67, 0x65, 0x52, 0x05, 0x65, 0x64, 0x67, 0x65, 0x73, 0x42, 0x0a, 0x0a, 0x08, 0x72, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xbc, 0x01, 0x0a, 0x04, 0x45, 0x64, 0x67, 0x65, 0x12,
0x28, 0x0a, 0x03, 0x73, 0x72, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c,
0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x52, 0x03, 0x73, 0x72, 0x63, 0x12, 0x28, 0x0a, 0x03, 0x64, 0x73, 0x74,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64,
0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x03,
0x64, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64,
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64,
0x12, 0x1b, 0x0a, 0x09, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20,
0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x49, 0x64, 0x12, 0x26, 0x0a,
0x0f, 0x6e, 0x6f, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x6d, 0x73, 0x67,
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x6f, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69,
0x74, 0x79, 0x4d, 0x73, 0x67, 0x22, 0xe2, 0x01, 0x0a, 0x10, 0x54, 0x6f, 0x70, 0x52, 0x6f, 0x75,
0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3b, 0x0a, 0x08, 0x73, 0x65,
0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6c,
0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x73,
0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x5f,
0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x69,
0x6d, 0x65, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x12, 0x29, 0x0a, 0x04, 0x6e, 0x6f, 0x6e, 0x65,
0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64,
0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x48, 0x00, 0x52, 0x04, 0x6e,
0x6f, 0x6e, 0x65, 0x12, 0x39, 0x0a, 0x0b, 0x74, 0x6f, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72,
0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65,
0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
0x48, 0x00, 0x52, 0x0a, 0x74, 0x6f, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x42, 0x0a,
0x0a, 0x08, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0xc2, 0x01, 0x0a, 0x11, 0x54,
0x6f, 0x70, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x33, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x1b, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x52,
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x48, 0x00, 0x52, 0x05,
0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x34, 0x0a, 0x02, 0x6f, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x22, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a,
0x2e, 0x54, 0x6f, 0x70, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x2e, 0x4f, 0x6b, 0x48, 0x00, 0x52, 0x02, 0x6f, 0x6b, 0x1a, 0x36, 0x0a, 0x02, 0x4f,
0x6b, 0x12, 0x30, 0x0a, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a,
0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x06, 0x72, 0x6f, 0x75,
0x74, 0x65, 0x73, 0x42, 0x0a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
0xe7, 0x01, 0x0a, 0x0a, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x30,
0x0a, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6c,
0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x52, 0x6f, 0x75, 0x74,
0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x2e, 0x52, 0x6f, 0x77, 0x52, 0x04, 0x72, 0x6f, 0x77, 0x73,
0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x1a, 0x8a, 0x01, 0x0a,
0x03, 0x52, 0x6f, 0x77, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x69,
0x6d, 0x65, 0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
0x0a, 0x74, 0x69, 0x6d, 0x65, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x12, 0x1c, 0x0a, 0x09, 0x61,
0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x2e, 0x0a, 0x05, 0x73, 0x74, 0x61,
0x74, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65,
0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x42, 0x61, 0x73, 0x69, 0x63, 0x53, 0x74, 0x61,
0x74, 0x73, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x22, 0xd2, 0x02, 0x0a, 0x0d, 0x47, 0x61,
0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x72,
0x6f, 0x77, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6c, 0x69, 0x6e, 0x6b,
0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79,
0x73, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x2e, 0x52, 0x6f, 0x77, 0x52, 0x04, 0x72, 0x6f, 0x77, 0x73,
0x1a, 0x8b, 0x02, 0x0a, 0x03, 0x52, 0x6f, 0x77, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65,
0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d,
0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c,
0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
0x52, 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a,
0x0f, 0x70, 0x61, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73,
0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x70, 0x61, 0x69, 0x72, 0x65, 0x64, 0x53, 0x65,
0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x18,
0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x12, 0x24, 0x0a, 0x0e,
0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6d, 0x73, 0x5f, 0x70, 0x35, 0x30, 0x18, 0x06,
0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x73, 0x50,
0x35, 0x30, 0x12, 0x24, 0x0a, 0x0e, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6d, 0x73,
0x5f, 0x70, 0x39, 0x35, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x6c, 0x61, 0x74, 0x65,
0x6e, 0x63, 0x79, 0x4d, 0x73, 0x50, 0x39, 0x35, 0x12, 0x24, 0x0a, 0x0e, 0x6c, 0x61, 0x74, 0x65,
0x6e, 0x63, 0x79, 0x5f, 0x6d, 0x73, 0x5f, 0x70, 0x39, 0x39, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04,
0x52, 0x0c, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x73, 0x50, 0x39, 0x39, 0x22, 0x8f,
0x01, 0x0a, 0x0f, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x6c, 0x75,
0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x11, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61,
0x6d, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x6e, 0x61,
0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x67,
0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12,
0x1f, 0x0a, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x18, 0x03,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77,
0x22, 0xd2, 0x01, 0x0a, 0x10, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x02, 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x21, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a,
0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x2e, 0x4f, 0x6b, 0x48, 0x00, 0x52, 0x02, 0x6f, 0x6b, 0x12, 0x33, 0x0a, 0x05, 0x65, 0x72,
0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6c, 0x69, 0x6e, 0x6b,
0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,
0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x1a,
0x48, 0x0a, 0x02, 0x4f, 0x6b, 0x12, 0x42, 0x0a, 0x0e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79,
0x73, 0x5f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e,
0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x47, 0x61, 0x74,
0x65, 0x77, 0x61, 0x79, 0x73, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x0d, 0x67, 0x61, 0x74, 0x65,
0x77, 0x61, 0x79, 0x73, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x72, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x2a, 0x0a, 0x0b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x53, 0x74,
0x61, 0x74, 0x75, 0x73, 0x12, 0x06, 0x0a, 0x02, 0x4f, 0x4b, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04,
0x46, 0x41, 0x49, 0x4c, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10,
0x02, 0x32, 0xf6, 0x04, 0x0a, 0x03, 0x41, 0x70, 0x69, 0x12, 0x54, 0x0a, 0x0b, 0x53, 0x74, 0x61,
0x74, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x20, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65,
0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x53, 0x75, 0x6d, 0x6d,
0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x6c, 0x69, 0x6e,
0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x53, 0x75,
0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12,
0x42, 0x0a, 0x05, 0x45, 0x64, 0x67, 0x65, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65,
0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x45, 0x64, 0x67, 0x65, 0x73, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e,
0x76, 0x69, 0x7a, 0x2e, 0x45, 0x64, 0x67, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x08, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x12,
0x1d, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x47,
0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e,
0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x47, 0x61,
0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
0x12, 0x4e, 0x0a, 0x09, 0x54, 0x6f, 0x70, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x1e, 0x2e,
0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x54, 0x6f, 0x70,
0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e,
0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x54, 0x6f, 0x70,
0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
0x12, 0x4b, 0x0a, 0x08, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6f, 0x64, 0x73, 0x12, 0x1d, 0x2e, 0x6c,
0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x4c, 0x69, 0x73, 0x74,
0x50, 0x6f, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x69,
0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50,
0x6f, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x57, 0x0a,
0x0c, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x21, 0x2e,
0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x4c, 0x69, 0x73,
0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x22, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e,
0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4e, 0x0a, 0x09, 0x53, 0x65, 0x6c, 0x66, 0x43, 0x68,
0x65, 0x63, 0x6b, 0x12, 0x1e, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76,
0x69, 0x7a, 0x2e, 0x53, 0x65, 0x6c, 0x66, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76,
0x69, 0x7a, 0x2e, 0x53, 0x65, 0x6c, 0x66, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x05, 0x41, 0x75, 0x74, 0x68, 0x7a, 0x12,
0x1a, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x41,
0x75, 0x74, 0x68, 0x7a, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x69,
0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x7a,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x35, 0x5a, 0x33, 0x67, 0x69,
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64,
0x2f, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2f, 0x76, 0x69, 0x7a, 0x2f, 0x6d, 0x65,
0x74, 0x72, 0x69, 0x63, 0x73, 0x2d, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x76, 0x69,
0x7a, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_viz_proto_rawDescOnce sync.Once
file_viz_proto_rawDescData = file_viz_proto_rawDesc
)
func file_viz_proto_rawDescGZIP() []byte {
file_viz_proto_rawDescOnce.Do(func() {
file_viz_proto_rawDescData = protoimpl.X.CompressGZIP(file_viz_proto_rawDescData)
})
return file_viz_proto_rawDescData
}
var file_viz_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
var file_viz_proto_msgTypes = make([]protoimpl.MessageInfo, 50)
var file_viz_proto_goTypes = []interface{}{
(CheckStatus)(0), // 0: linkerd2.viz.CheckStatus
(HttpMethod_Registered)(0), // 1: linkerd2.viz.HttpMethod.Registered
(Scheme_Registered)(0), // 2: linkerd2.viz.Scheme.Registered
(*Empty)(nil), // 3: linkerd2.viz.Empty
(*CheckResult)(nil), // 4: linkerd2.viz.CheckResult
(*SelfCheckRequest)(nil), // 5: linkerd2.viz.SelfCheckRequest
(*SelfCheckResponse)(nil), // 6: linkerd2.viz.SelfCheckResponse
(*ListServicesRequest)(nil), // 7: linkerd2.viz.ListServicesRequest
(*ListServicesResponse)(nil), // 8: linkerd2.viz.ListServicesResponse
(*Service)(nil), // 9: linkerd2.viz.Service
(*ListPodsRequest)(nil), // 10: linkerd2.viz.ListPodsRequest
(*ListPodsResponse)(nil), // 11: linkerd2.viz.ListPodsResponse
(*Pod)(nil), // 12: linkerd2.viz.Pod
(*HttpMethod)(nil), // 13: linkerd2.viz.HttpMethod
(*Scheme)(nil), // 14: linkerd2.viz.Scheme
(*Headers)(nil), // 15: linkerd2.viz.Headers
(*Eos)(nil), // 16: linkerd2.viz.Eos
(*ApiError)(nil), // 17: linkerd2.viz.ApiError
(*PodErrors)(nil), // 18: linkerd2.viz.PodErrors
(*Resource)(nil), // 19: linkerd2.viz.Resource
(*ResourceSelection)(nil), // 20: linkerd2.viz.ResourceSelection
(*ResourceError)(nil), // 21: linkerd2.viz.ResourceError
(*StatSummaryRequest)(nil), // 22: linkerd2.viz.StatSummaryRequest
(*StatSummaryResponse)(nil), // 23: linkerd2.viz.StatSummaryResponse
(*AuthzRequest)(nil), // 24: linkerd2.viz.AuthzRequest
(*AuthzResponse)(nil), // 25: linkerd2.viz.AuthzResponse
(*BasicStats)(nil), // 26: linkerd2.viz.BasicStats
(*TcpStats)(nil), // 27: linkerd2.viz.TcpStats
(*TrafficSplitStats)(nil), // 28: linkerd2.viz.TrafficSplitStats
(*ServerStats)(nil), // 29: linkerd2.viz.ServerStats
(*StatTable)(nil), // 30: linkerd2.viz.StatTable
(*EdgesRequest)(nil), // 31: linkerd2.viz.EdgesRequest
(*EdgesResponse)(nil), // 32: linkerd2.viz.EdgesResponse
(*Edge)(nil), // 33: linkerd2.viz.Edge
(*TopRoutesRequest)(nil), // 34: linkerd2.viz.TopRoutesRequest
(*TopRoutesResponse)(nil), // 35: linkerd2.viz.TopRoutesResponse
(*RouteTable)(nil), // 36: linkerd2.viz.RouteTable
(*GatewaysTable)(nil), // 37: linkerd2.viz.GatewaysTable
(*GatewaysRequest)(nil), // 38: linkerd2.viz.GatewaysRequest
(*GatewaysResponse)(nil), // 39: linkerd2.viz.GatewaysResponse
(*Headers_Header)(nil), // 40: linkerd2.viz.Headers.Header
(*PodErrors_PodError)(nil), // 41: linkerd2.viz.PodErrors.PodError
(*PodErrors_PodError_ContainerError)(nil), // 42: linkerd2.viz.PodErrors.PodError.ContainerError
(*StatSummaryResponse_Ok)(nil), // 43: linkerd2.viz.StatSummaryResponse.Ok
(*AuthzResponse_Ok)(nil), // 44: linkerd2.viz.AuthzResponse.Ok
(*StatTable_PodGroup)(nil), // 45: linkerd2.viz.StatTable.PodGroup
(*StatTable_PodGroup_Row)(nil), // 46: linkerd2.viz.StatTable.PodGroup.Row
nil, // 47: linkerd2.viz.StatTable.PodGroup.Row.ErrorsByPodEntry
(*EdgesResponse_Ok)(nil), // 48: linkerd2.viz.EdgesResponse.Ok
(*TopRoutesResponse_Ok)(nil), // 49: linkerd2.viz.TopRoutesResponse.Ok
(*RouteTable_Row)(nil), // 50: linkerd2.viz.RouteTable.Row
(*GatewaysTable_Row)(nil), // 51: linkerd2.viz.GatewaysTable.Row
(*GatewaysResponse_Ok)(nil), // 52: linkerd2.viz.GatewaysResponse.Ok
(*duration.Duration)(nil), // 53: google.protobuf.Duration
}
var file_viz_proto_depIdxs = []int32{
0, // 0: linkerd2.viz.CheckResult.Status:type_name -> linkerd2.viz.CheckStatus
4, // 1: linkerd2.viz.SelfCheckResponse.results:type_name -> linkerd2.viz.CheckResult
9, // 2: linkerd2.viz.ListServicesResponse.services:type_name -> linkerd2.viz.Service
20, // 3: linkerd2.viz.ListPodsRequest.selector:type_name -> linkerd2.viz.ResourceSelection
12, // 4: linkerd2.viz.ListPodsResponse.pods:type_name -> linkerd2.viz.Pod
53, // 5: linkerd2.viz.Pod.sinceLastReport:type_name -> google.protobuf.Duration
53, // 6: linkerd2.viz.Pod.uptime:type_name -> google.protobuf.Duration
1, // 7: linkerd2.viz.HttpMethod.registered:type_name -> linkerd2.viz.HttpMethod.Registered
2, // 8: linkerd2.viz.Scheme.registered:type_name -> linkerd2.viz.Scheme.Registered
40, // 9: linkerd2.viz.Headers.headers:type_name -> linkerd2.viz.Headers.Header
41, // 10: linkerd2.viz.PodErrors.errors:type_name -> linkerd2.viz.PodErrors.PodError
19, // 11: linkerd2.viz.ResourceSelection.resource:type_name -> linkerd2.viz.Resource
19, // 12: linkerd2.viz.ResourceError.resource:type_name -> linkerd2.viz.Resource
20, // 13: linkerd2.viz.StatSummaryRequest.selector:type_name -> linkerd2.viz.ResourceSelection
3, // 14: linkerd2.viz.StatSummaryRequest.none:type_name -> linkerd2.viz.Empty
19, // 15: linkerd2.viz.StatSummaryRequest.to_resource:type_name -> linkerd2.viz.Resource
19, // 16: linkerd2.viz.StatSummaryRequest.from_resource:type_name -> linkerd2.viz.Resource
43, // 17: linkerd2.viz.StatSummaryResponse.ok:type_name -> linkerd2.viz.StatSummaryResponse.Ok
21, // 18: linkerd2.viz.StatSummaryResponse.error:type_name -> linkerd2.viz.ResourceError
19, // 19: linkerd2.viz.AuthzRequest.resource:type_name -> linkerd2.viz.Resource
44, // 20: linkerd2.viz.AuthzResponse.ok:type_name -> linkerd2.viz.AuthzResponse.Ok
21, // 21: linkerd2.viz.AuthzResponse.error:type_name -> linkerd2.viz.ResourceError
19, // 22: linkerd2.viz.ServerStats.srv:type_name -> linkerd2.viz.Resource
19, // 23: linkerd2.viz.ServerStats.route:type_name -> linkerd2.viz.Resource
19, // 24: linkerd2.viz.ServerStats.authz:type_name -> linkerd2.viz.Resource
45, // 25: linkerd2.viz.StatTable.pod_group:type_name -> linkerd2.viz.StatTable.PodGroup
20, // 26: linkerd2.viz.EdgesRequest.selector:type_name -> linkerd2.viz.ResourceSelection
48, // 27: linkerd2.viz.EdgesResponse.ok:type_name -> linkerd2.viz.EdgesResponse.Ok
21, // 28: linkerd2.viz.EdgesResponse.error:type_name -> linkerd2.viz.ResourceError
19, // 29: linkerd2.viz.Edge.src:type_name -> linkerd2.viz.Resource
19, // 30: linkerd2.viz.Edge.dst:type_name -> linkerd2.viz.Resource
20, // 31: linkerd2.viz.TopRoutesRequest.selector:type_name -> linkerd2.viz.ResourceSelection
3, // 32: linkerd2.viz.TopRoutesRequest.none:type_name -> linkerd2.viz.Empty
19, // 33: linkerd2.viz.TopRoutesRequest.to_resource:type_name -> linkerd2.viz.Resource
21, // 34: linkerd2.viz.TopRoutesResponse.error:type_name -> linkerd2.viz.ResourceError
49, // 35: linkerd2.viz.TopRoutesResponse.ok:type_name -> linkerd2.viz.TopRoutesResponse.Ok
50, // 36: linkerd2.viz.RouteTable.rows:type_name -> linkerd2.viz.RouteTable.Row
51, // 37: linkerd2.viz.GatewaysTable.rows:type_name -> linkerd2.viz.GatewaysTable.Row
52, // 38: linkerd2.viz.GatewaysResponse.ok:type_name -> linkerd2.viz.GatewaysResponse.Ok
21, // 39: linkerd2.viz.GatewaysResponse.error:type_name -> linkerd2.viz.ResourceError
42, // 40: linkerd2.viz.PodErrors.PodError.container:type_name -> linkerd2.viz.PodErrors.PodError.ContainerError
30, // 41: linkerd2.viz.StatSummaryResponse.Ok.stat_tables:type_name -> linkerd2.viz.StatTable
30, // 42: linkerd2.viz.AuthzResponse.Ok.stat_table:type_name -> linkerd2.viz.StatTable
46, // 43: linkerd2.viz.StatTable.PodGroup.rows:type_name -> linkerd2.viz.StatTable.PodGroup.Row
19, // 44: linkerd2.viz.StatTable.PodGroup.Row.resource:type_name -> linkerd2.viz.Resource
26, // 45: linkerd2.viz.StatTable.PodGroup.Row.stats:type_name -> linkerd2.viz.BasicStats
27, // 46: linkerd2.viz.StatTable.PodGroup.Row.tcp_stats:type_name -> linkerd2.viz.TcpStats
28, // 47: linkerd2.viz.StatTable.PodGroup.Row.ts_stats:type_name -> linkerd2.viz.TrafficSplitStats
29, // 48: linkerd2.viz.StatTable.PodGroup.Row.srv_stats:type_name -> linkerd2.viz.ServerStats
47, // 49: linkerd2.viz.StatTable.PodGroup.Row.errors_by_pod:type_name -> linkerd2.viz.StatTable.PodGroup.Row.ErrorsByPodEntry
18, // 50: linkerd2.viz.StatTable.PodGroup.Row.ErrorsByPodEntry.value:type_name -> linkerd2.viz.PodErrors
33, // 51: linkerd2.viz.EdgesResponse.Ok.edges:type_name -> linkerd2.viz.Edge
36, // 52: linkerd2.viz.TopRoutesResponse.Ok.routes:type_name -> linkerd2.viz.RouteTable
26, // 53: linkerd2.viz.RouteTable.Row.stats:type_name -> linkerd2.viz.BasicStats
37, // 54: linkerd2.viz.GatewaysResponse.Ok.gateways_table:type_name -> linkerd2.viz.GatewaysTable
22, // 55: linkerd2.viz.Api.StatSummary:input_type -> linkerd2.viz.StatSummaryRequest
31, // 56: linkerd2.viz.Api.Edges:input_type -> linkerd2.viz.EdgesRequest
38, // 57: linkerd2.viz.Api.Gateways:input_type -> linkerd2.viz.GatewaysRequest
34, // 58: linkerd2.viz.Api.TopRoutes:input_type -> linkerd2.viz.TopRoutesRequest
10, // 59: linkerd2.viz.Api.ListPods:input_type -> linkerd2.viz.ListPodsRequest
7, // 60: linkerd2.viz.Api.ListServices:input_type -> linkerd2.viz.ListServicesRequest
5, // 61: linkerd2.viz.Api.SelfCheck:input_type -> linkerd2.viz.SelfCheckRequest
24, // 62: linkerd2.viz.Api.Authz:input_type -> linkerd2.viz.AuthzRequest
23, // 63: linkerd2.viz.Api.StatSummary:output_type -> linkerd2.viz.StatSummaryResponse
32, // 64: linkerd2.viz.Api.Edges:output_type -> linkerd2.viz.EdgesResponse
39, // 65: linkerd2.viz.Api.Gateways:output_type -> linkerd2.viz.GatewaysResponse
35, // 66: linkerd2.viz.Api.TopRoutes:output_type -> linkerd2.viz.TopRoutesResponse
11, // 67: linkerd2.viz.Api.ListPods:output_type -> linkerd2.viz.ListPodsResponse
8, // 68: linkerd2.viz.Api.ListServices:output_type -> linkerd2.viz.ListServicesResponse
6, // 69: linkerd2.viz.Api.SelfCheck:output_type -> linkerd2.viz.SelfCheckResponse
25, // 70: linkerd2.viz.Api.Authz:output_type -> linkerd2.viz.AuthzResponse
63, // [63:71] is the sub-list for method output_type
55, // [55:63] is the sub-list for method input_type
55, // [55:55] is the sub-list for extension type_name
55, // [55:55] is the sub-list for extension extendee
0, // [0:55] is the sub-list for field type_name
}
func init() { file_viz_proto_init() }
func file_viz_proto_init() {
if File_viz_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_viz_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Empty); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*CheckResult); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SelfCheckRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SelfCheckResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListServicesRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListServicesResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Service); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListPodsRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListPodsResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Pod); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HttpMethod); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Scheme); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Headers); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Eos); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ApiError); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PodErrors); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Resource); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ResourceSelection); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ResourceError); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*StatSummaryRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*StatSummaryResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AuthzRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AuthzResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*BasicStats); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TcpStats); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TrafficSplitStats); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ServerStats); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*StatTable); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*EdgesRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*EdgesResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Edge); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TopRoutesRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TopRoutesResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RouteTable); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GatewaysTable); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GatewaysRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GatewaysResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Headers_Header); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PodErrors_PodError); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[39].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PodErrors_PodError_ContainerError); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[40].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*StatSummaryResponse_Ok); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[41].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AuthzResponse_Ok); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[42].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*StatTable_PodGroup); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[43].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*StatTable_PodGroup_Row); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[45].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*EdgesResponse_Ok); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[46].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TopRoutesResponse_Ok); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[47].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RouteTable_Row); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[48].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GatewaysTable_Row); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_viz_proto_msgTypes[49].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GatewaysResponse_Ok); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_viz_proto_msgTypes[9].OneofWrappers = []interface{}{
(*Pod_Deployment)(nil),
(*Pod_ReplicaSet)(nil),
(*Pod_ReplicationController)(nil),
(*Pod_StatefulSet)(nil),
(*Pod_DaemonSet)(nil),
(*Pod_Job)(nil),
}
file_viz_proto_msgTypes[10].OneofWrappers = []interface{}{
(*HttpMethod_Registered_)(nil),
(*HttpMethod_Unregistered)(nil),
}
file_viz_proto_msgTypes[11].OneofWrappers = []interface{}{
(*Scheme_Registered_)(nil),
(*Scheme_Unregistered)(nil),
}
file_viz_proto_msgTypes[13].OneofWrappers = []interface{}{
(*Eos_GrpcStatusCode)(nil),
(*Eos_ResetErrorCode)(nil),
}
file_viz_proto_msgTypes[19].OneofWrappers = []interface{}{
(*StatSummaryRequest_None)(nil),
(*StatSummaryRequest_ToResource)(nil),
(*StatSummaryRequest_FromResource)(nil),
}
file_viz_proto_msgTypes[20].OneofWrappers = []interface{}{
(*StatSummaryResponse_Ok_)(nil),
(*StatSummaryResponse_Error)(nil),
}
file_viz_proto_msgTypes[22].OneofWrappers = []interface{}{
(*AuthzResponse_Ok_)(nil),
(*AuthzResponse_Error)(nil),
}
file_viz_proto_msgTypes[27].OneofWrappers = []interface{}{
(*StatTable_PodGroup_)(nil),
}
file_viz_proto_msgTypes[29].OneofWrappers = []interface{}{
(*EdgesResponse_Ok_)(nil),
(*EdgesResponse_Error)(nil),
}
file_viz_proto_msgTypes[31].OneofWrappers = []interface{}{
(*TopRoutesRequest_None)(nil),
(*TopRoutesRequest_ToResource)(nil),
}
file_viz_proto_msgTypes[32].OneofWrappers = []interface{}{
(*TopRoutesResponse_Error)(nil),
(*TopRoutesResponse_Ok_)(nil),
}
file_viz_proto_msgTypes[36].OneofWrappers = []interface{}{
(*GatewaysResponse_Ok_)(nil),
(*GatewaysResponse_Error)(nil),
}
file_viz_proto_msgTypes[37].OneofWrappers = []interface{}{
(*Headers_Header_ValueStr)(nil),
(*Headers_Header_ValueBin)(nil),
}
file_viz_proto_msgTypes[38].OneofWrappers = []interface{}{
(*PodErrors_PodError_Container)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_viz_proto_rawDesc,
NumEnums: 3,
NumMessages: 50,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_viz_proto_goTypes,
DependencyIndexes: file_viz_proto_depIdxs,
EnumInfos: file_viz_proto_enumTypes,
MessageInfos: file_viz_proto_msgTypes,
}.Build()
File_viz_proto = out.File
file_viz_proto_rawDesc = nil
file_viz_proto_goTypes = nil
file_viz_proto_depIdxs = nil
}
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc v3.20.0
// source: viz.proto
package viz
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// ApiClient is the client API for Api service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type ApiClient interface {
StatSummary(ctx context.Context, in *StatSummaryRequest, opts ...grpc.CallOption) (*StatSummaryResponse, error)
Edges(ctx context.Context, in *EdgesRequest, opts ...grpc.CallOption) (*EdgesResponse, error)
Gateways(ctx context.Context, in *GatewaysRequest, opts ...grpc.CallOption) (*GatewaysResponse, error)
TopRoutes(ctx context.Context, in *TopRoutesRequest, opts ...grpc.CallOption) (*TopRoutesResponse, error)
ListPods(ctx context.Context, in *ListPodsRequest, opts ...grpc.CallOption) (*ListPodsResponse, error)
ListServices(ctx context.Context, in *ListServicesRequest, opts ...grpc.CallOption) (*ListServicesResponse, error)
SelfCheck(ctx context.Context, in *SelfCheckRequest, opts ...grpc.CallOption) (*SelfCheckResponse, error)
Authz(ctx context.Context, in *AuthzRequest, opts ...grpc.CallOption) (*AuthzResponse, error)
}
type apiClient struct {
cc grpc.ClientConnInterface
}
func NewApiClient(cc grpc.ClientConnInterface) ApiClient {
return &apiClient{cc}
}
func (c *apiClient) StatSummary(ctx context.Context, in *StatSummaryRequest, opts ...grpc.CallOption) (*StatSummaryResponse, error) {
out := new(StatSummaryResponse)
err := c.cc.Invoke(ctx, "/linkerd2.viz.Api/StatSummary", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *apiClient) Edges(ctx context.Context, in *EdgesRequest, opts ...grpc.CallOption) (*EdgesResponse, error) {
out := new(EdgesResponse)
err := c.cc.Invoke(ctx, "/linkerd2.viz.Api/Edges", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *apiClient) Gateways(ctx context.Context, in *GatewaysRequest, opts ...grpc.CallOption) (*GatewaysResponse, error) {
out := new(GatewaysResponse)
err := c.cc.Invoke(ctx, "/linkerd2.viz.Api/Gateways", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *apiClient) TopRoutes(ctx context.Context, in *TopRoutesRequest, opts ...grpc.CallOption) (*TopRoutesResponse, error) {
out := new(TopRoutesResponse)
err := c.cc.Invoke(ctx, "/linkerd2.viz.Api/TopRoutes", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *apiClient) ListPods(ctx context.Context, in *ListPodsRequest, opts ...grpc.CallOption) (*ListPodsResponse, error) {
out := new(ListPodsResponse)
err := c.cc.Invoke(ctx, "/linkerd2.viz.Api/ListPods", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *apiClient) ListServices(ctx context.Context, in *ListServicesRequest, opts ...grpc.CallOption) (*ListServicesResponse, error) {
out := new(ListServicesResponse)
err := c.cc.Invoke(ctx, "/linkerd2.viz.Api/ListServices", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *apiClient) SelfCheck(ctx context.Context, in *SelfCheckRequest, opts ...grpc.CallOption) (*SelfCheckResponse, error) {
out := new(SelfCheckResponse)
err := c.cc.Invoke(ctx, "/linkerd2.viz.Api/SelfCheck", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *apiClient) Authz(ctx context.Context, in *AuthzRequest, opts ...grpc.CallOption) (*AuthzResponse, error) {
out := new(AuthzResponse)
err := c.cc.Invoke(ctx, "/linkerd2.viz.Api/Authz", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// ApiServer is the server API for Api service.
// All implementations must embed UnimplementedApiServer
// for forward compatibility
type ApiServer interface {
StatSummary(context.Context, *StatSummaryRequest) (*StatSummaryResponse, error)
Edges(context.Context, *EdgesRequest) (*EdgesResponse, error)
Gateways(context.Context, *GatewaysRequest) (*GatewaysResponse, error)
TopRoutes(context.Context, *TopRoutesRequest) (*TopRoutesResponse, error)
ListPods(context.Context, *ListPodsRequest) (*ListPodsResponse, error)
ListServices(context.Context, *ListServicesRequest) (*ListServicesResponse, error)
SelfCheck(context.Context, *SelfCheckRequest) (*SelfCheckResponse, error)
Authz(context.Context, *AuthzRequest) (*AuthzResponse, error)
mustEmbedUnimplementedApiServer()
}
// UnimplementedApiServer must be embedded to have forward compatible implementations.
type UnimplementedApiServer struct {
}
func (UnimplementedApiServer) StatSummary(context.Context, *StatSummaryRequest) (*StatSummaryResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method StatSummary not implemented")
}
func (UnimplementedApiServer) Edges(context.Context, *EdgesRequest) (*EdgesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Edges not implemented")
}
func (UnimplementedApiServer) Gateways(context.Context, *GatewaysRequest) (*GatewaysResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Gateways not implemented")
}
func (UnimplementedApiServer) TopRoutes(context.Context, *TopRoutesRequest) (*TopRoutesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method TopRoutes not implemented")
}
func (UnimplementedApiServer) ListPods(context.Context, *ListPodsRequest) (*ListPodsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListPods not implemented")
}
func (UnimplementedApiServer) ListServices(context.Context, *ListServicesRequest) (*ListServicesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListServices not implemented")
}
func (UnimplementedApiServer) SelfCheck(context.Context, *SelfCheckRequest) (*SelfCheckResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SelfCheck not implemented")
}
func (UnimplementedApiServer) Authz(context.Context, *AuthzRequest) (*AuthzResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Authz not implemented")
}
func (UnimplementedApiServer) mustEmbedUnimplementedApiServer() {}
// UnsafeApiServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to ApiServer will
// result in compilation errors.
type UnsafeApiServer interface {
mustEmbedUnimplementedApiServer()
}
func RegisterApiServer(s grpc.ServiceRegistrar, srv ApiServer) {
s.RegisterService(&Api_ServiceDesc, srv)
}
func _Api_StatSummary_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(StatSummaryRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ApiServer).StatSummary(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/linkerd2.viz.Api/StatSummary",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ApiServer).StatSummary(ctx, req.(*StatSummaryRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Api_Edges_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(EdgesRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ApiServer).Edges(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/linkerd2.viz.Api/Edges",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ApiServer).Edges(ctx, req.(*EdgesRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Api_Gateways_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GatewaysRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ApiServer).Gateways(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/linkerd2.viz.Api/Gateways",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ApiServer).Gateways(ctx, req.(*GatewaysRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Api_TopRoutes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(TopRoutesRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ApiServer).TopRoutes(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/linkerd2.viz.Api/TopRoutes",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ApiServer).TopRoutes(ctx, req.(*TopRoutesRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Api_ListPods_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListPodsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ApiServer).ListPods(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/linkerd2.viz.Api/ListPods",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ApiServer).ListPods(ctx, req.(*ListPodsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Api_ListServices_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListServicesRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ApiServer).ListServices(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/linkerd2.viz.Api/ListServices",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ApiServer).ListServices(ctx, req.(*ListServicesRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Api_SelfCheck_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SelfCheckRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ApiServer).SelfCheck(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/linkerd2.viz.Api/SelfCheck",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ApiServer).SelfCheck(ctx, req.(*SelfCheckRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Api_Authz_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AuthzRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ApiServer).Authz(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/linkerd2.viz.Api/Authz",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ApiServer).Authz(ctx, req.(*AuthzRequest))
}
return interceptor(ctx, in, info, handler)
}
// Api_ServiceDesc is the grpc.ServiceDesc for Api service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Api_ServiceDesc = grpc.ServiceDesc{
ServiceName: "linkerd2.viz.Api",
HandlerType: (*ApiServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "StatSummary",
Handler: _Api_StatSummary_Handler,
},
{
MethodName: "Edges",
Handler: _Api_Edges_Handler,
},
{
MethodName: "Gateways",
Handler: _Api_Gateways_Handler,
},
{
MethodName: "TopRoutes",
Handler: _Api_TopRoutes_Handler,
},
{
MethodName: "ListPods",
Handler: _Api_ListPods_Handler,
},
{
MethodName: "ListServices",
Handler: _Api_ListServices_Handler,
},
{
MethodName: "SelfCheck",
Handler: _Api_SelfCheck_Handler,
},
{
MethodName: "Authz",
Handler: _Api_Authz_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "viz.proto",
}
package healthcheck
import (
"context"
"crypto/x509"
"errors"
"fmt"
"strings"
"github.com/linkerd/linkerd2/pkg/healthcheck"
"github.com/linkerd/linkerd2/pkg/k8s"
"github.com/linkerd/linkerd2/pkg/tls"
"github.com/linkerd/linkerd2/pkg/version"
"github.com/linkerd/linkerd2/viz/metrics-api/client"
pb "github.com/linkerd/linkerd2/viz/metrics-api/gen/viz"
"github.com/linkerd/linkerd2/viz/pkg/labels"
corev1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
apiregistrationv1client "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1"
)
const (
// VizExtensionName is the name of the viz extension
VizExtensionName = "viz"
// LinkerdVizExtensionCheck adds checks related to the Linkerd Viz extension
LinkerdVizExtensionCheck healthcheck.CategoryID = "linkerd-viz"
// LinkerdVizExtensionDataPlaneCheck adds checks related to dataplane for the linkerd-viz extension
LinkerdVizExtensionDataPlaneCheck healthcheck.CategoryID = "linkerd-viz-data-plane"
tapTLSSecretName = "tap-k8s-tls"
tapOldTLSSecretName = "linkerd-tap-tls"
// linkerdTapAPIServiceName is the name of the tap api service
// This key is passed to checkApiService method to check whether
// the api service is available or not
linkerdTapAPIServiceName = "v1alpha1.tap.linkerd.io"
)
type VizOptions struct {
*healthcheck.Options
VizNamespaceOverride string
}
// HealthChecker wraps Linkerd's main healthchecker, adding extra fields for Viz
type HealthChecker struct {
*healthcheck.HealthChecker
vizAPIClient pb.ApiClient
vizNamespace string
externalPrometheusURL string
vizNamespaceOverride string
}
// NewHealthChecker returns an initialized HealthChecker for Viz
// The parentCheckIDs are the category IDs of the linkerd core checks that
// are to be ran together with this instance
// The returned instance does not contain any of the viz Categories and
// to be explicitly added by using hc.AppendCategories
func NewHealthChecker(parentCheckIDs []healthcheck.CategoryID, options *VizOptions) *HealthChecker {
parentHC := healthcheck.NewHealthChecker(parentCheckIDs, options.Options)
return &HealthChecker{
HealthChecker: parentHC,
vizNamespaceOverride: options.VizNamespaceOverride,
}
}
// VizAPIClient returns a fully configured Viz API client
func (hc *HealthChecker) VizAPIClient() pb.ApiClient {
return hc.vizAPIClient
}
// RunChecks implements the healthcheck.Runner interface
func (hc *HealthChecker) RunChecks(observer healthcheck.CheckObserver) (bool, bool) {
return hc.HealthChecker.RunChecks(observer)
}
// VizCategory returns a healthcheck.Category containing checkers
// to verify the health of viz components
// fullCheck parameter will decide to run a full or a smaller set of healthchecks.
func (hc *HealthChecker) VizCategory(fullCheck bool) *healthcheck.Category {
vizSelector := fmt.Sprintf("%s=%s", k8s.LinkerdExtensionLabel, VizExtensionName)
checks := []healthcheck.Checker{
*healthcheck.NewChecker("linkerd-viz Namespace exists").
WithHintAnchor("l5d-viz-ns-exists").
Fatal().
WithCheck(func(ctx context.Context) error {
if hc.vizNamespaceOverride == "" {
vizNs, err := hc.KubeAPIClient().GetNamespaceWithExtensionLabel(ctx, "viz")
if err != nil {
return err
}
hc.vizNamespace = vizNs.Name
hc.externalPrometheusURL = vizNs.Annotations[labels.VizExternalPrometheus]
return nil
}
vizNs, err := hc.KubeAPIClient().GetNamespace(ctx, hc.vizNamespaceOverride)
if err != nil {
return err
}
if vizNs.Labels[k8s.LinkerdExtensionLabel] != "viz" {
return errors.New("This is not a linkerd-viz namespace")
}
hc.vizNamespace = vizNs.Name
hc.externalPrometheusURL = vizNs.Annotations[labels.VizExternalPrometheus]
return nil
}),
*healthcheck.NewChecker("can initialize the client").
WithHintAnchor("l5d-viz-existence-client").
Fatal().
WithRetryDeadline(hc.RetryDeadline).
WithCheck(func(ctx context.Context) (err error) {
if hc.APIAddr != "" {
hc.vizAPIClient, err = client.NewInternalClient(hc.APIAddr)
} else {
hc.vizAPIClient, err = client.NewExternalClient(ctx, hc.vizNamespace, hc.KubeAPIClient())
}
return
})}
if !fullCheck {
return healthcheck.NewCategory(LinkerdVizExtensionCheck, checks, true)
}
checks = append(checks,
*healthcheck.NewChecker("linkerd-viz ClusterRoles exist").
WithHintAnchor("l5d-viz-cr-exists").
Fatal().
WithCheck(func(ctx context.Context) error {
return healthcheck.CheckClusterRoles(ctx, hc.KubeAPIClient(), true, []string{fmt.Sprintf("linkerd-%s-tap", hc.vizNamespace), fmt.Sprintf("linkerd-%s-metrics-api", hc.vizNamespace), fmt.Sprintf("linkerd-%s-tap-admin", hc.vizNamespace), "linkerd-tap-injector"}, "")
}),
*healthcheck.NewChecker("linkerd-viz ClusterRoleBindings exist").
WithHintAnchor("l5d-viz-crb-exists").
Fatal().
WithCheck(func(ctx context.Context) error {
return healthcheck.CheckClusterRoleBindings(ctx, hc.KubeAPIClient(), true, []string{fmt.Sprintf("linkerd-%s-tap", hc.vizNamespace), fmt.Sprintf("linkerd-%s-metrics-api", hc.vizNamespace), fmt.Sprintf("linkerd-%s-tap-auth-delegator", hc.vizNamespace), "linkerd-tap-injector"}, "")
}),
*healthcheck.NewChecker("tap API server has valid cert").
WithHintAnchor("l5d-tap-cert-valid").
Fatal().
WithCheck(func(ctx context.Context) error {
anchors, err := fetchTapCaBundle(ctx, hc.KubeAPIClient())
if err != nil {
return err
}
cert, err := hc.FetchCredsFromSecret(ctx, hc.vizNamespace, tapTLSSecretName)
if kerrors.IsNotFound(err) {
cert, err = hc.FetchCredsFromOldSecret(ctx, hc.vizNamespace, tapOldTLSSecretName)
}
if err != nil {
return err
}
identityName := fmt.Sprintf("tap.%s.svc", hc.vizNamespace)
return hc.CheckCertAndAnchors(cert, anchors, identityName)
}),
*healthcheck.NewChecker("tap API server cert is valid for at least 60 days").
WithHintAnchor("l5d-tap-cert-not-expiring-soon").
Warning().
WithCheck(func(ctx context.Context) error {
cert, err := hc.FetchCredsFromSecret(ctx, hc.vizNamespace, tapTLSSecretName)
if kerrors.IsNotFound(err) {
cert, err = hc.FetchCredsFromOldSecret(ctx, hc.vizNamespace, tapOldTLSSecretName)
}
if err != nil {
return err
}
return hc.CheckCertAndAnchorsExpiringSoon(cert)
}),
*healthcheck.NewChecker("tap API service is running").
WithHintAnchor("l5d-tap-api").
Warning().
WithRetryDeadline(hc.RetryDeadline).
WithCheck(func(ctx context.Context) error {
return hc.CheckAPIService(ctx, linkerdTapAPIServiceName)
}),
*healthcheck.NewChecker("linkerd-viz pods are injected").
WithHintAnchor("l5d-viz-pods-injection").
Warning().
WithCheck(func(ctx context.Context) error {
pods, err := hc.KubeAPIClient().GetPodsByNamespace(ctx, hc.vizNamespace)
if err != nil {
return err
}
return healthcheck.CheckIfDataPlanePodsExist(pods)
}),
*healthcheck.NewChecker("viz extension pods are running").
WithHintAnchor("l5d-viz-pods-running").
Warning().
WithRetryDeadline(hc.RetryDeadline).
SurfaceErrorOnRetry().
WithCheck(func(ctx context.Context) error {
podList, err := hc.KubeAPIClient().CoreV1().Pods(hc.vizNamespace).List(ctx, metav1.ListOptions{
LabelSelector: vizSelector,
})
if err != nil {
return err
}
// Check for relevant pods to be present
err = healthcheck.CheckForPods(podList.Items, []string{"web", "tap", "metrics-api", "tap-injector"})
if err != nil {
return err
}
return healthcheck.CheckPodsRunning(podList.Items, hc.vizNamespace)
}),
*healthcheck.NewChecker("viz extension proxies are healthy").
WithHintAnchor("l5d-viz-proxy-healthy").
Warning().
WithCheck(func(ctx context.Context) (err error) {
return hc.CheckProxyHealth(ctx, hc.ControlPlaneNamespace, hc.vizNamespace)
}),
*healthcheck.NewChecker("viz extension proxies are up-to-date").
WithHintAnchor("l5d-viz-proxy-cp-version").
Warning().
WithCheck(func(ctx context.Context) error {
var err error
if hc.VersionOverride != "" {
hc.LatestVersions, err = version.NewChannels(hc.VersionOverride)
} else {
uuid := "unknown"
if hc.UUID() != "" {
uuid = hc.UUID()
}
hc.LatestVersions, err = version.GetLatestVersions(ctx, uuid, "cli")
}
if err != nil {
return err
}
pods, err := hc.KubeAPIClient().GetPodsByNamespace(ctx, hc.vizNamespace)
if err != nil {
return err
}
return hc.CheckProxyVersionsUpToDate(pods)
}),
*healthcheck.NewChecker("viz extension proxies and cli versions match").
WithHintAnchor("l5d-viz-proxy-cli-version").
Warning().
WithCheck(func(ctx context.Context) error {
pods, err := hc.KubeAPIClient().GetPodsByNamespace(ctx, hc.vizNamespace)
if err != nil {
return err
}
return healthcheck.CheckIfProxyVersionsMatchWithCLI(pods)
}),
*healthcheck.NewChecker("prometheus is installed and configured correctly").
WithHintAnchor("l5d-viz-prometheus").
Warning().
WithCheck(func(ctx context.Context) error {
if hc.externalPrometheusURL != "" {
return healthcheck.SkipError{Reason: "prometheus is disabled"}
}
// Check for ClusterRoles
err := healthcheck.CheckClusterRoles(ctx, hc.KubeAPIClient(), true, []string{fmt.Sprintf("linkerd-%s-prometheus", hc.vizNamespace)}, "")
if err != nil {
return err
}
// Check for ClusterRoleBindings
err = healthcheck.CheckClusterRoleBindings(ctx, hc.KubeAPIClient(), true, []string{fmt.Sprintf("linkerd-%s-prometheus", hc.vizNamespace)}, "")
if err != nil {
return err
}
// Check for ConfigMap
err = healthcheck.CheckConfigMaps(ctx, hc.KubeAPIClient(), hc.vizNamespace, true, []string{"prometheus-config"}, "")
if err != nil {
return err
}
// Check for relevant pods to be present
podList, err := hc.KubeAPIClient().CoreV1().Pods(hc.vizNamespace).List(ctx, metav1.ListOptions{
LabelSelector: vizSelector,
})
if err != nil {
return err
}
return healthcheck.CheckForPods(podList.Items, []string{"prometheus"})
}),
*healthcheck.NewChecker("viz extension self-check").
WithHintAnchor("l5d-viz-metrics-api").
Fatal().
// to avoid confusing users with a prometheus readiness error, we only show
// "waiting for check to complete" while things converge. If after the timeout
// it still hasn't converged, we show the real error (a 503 usually).
WithRetryDeadline(hc.RetryDeadline).
WithCheck(func(ctx context.Context) error {
results, err := hc.vizAPIClient.SelfCheck(ctx, &pb.SelfCheckRequest{})
if err != nil {
return err
}
if len(results.GetResults()) == 0 {
return errors.New("No results returned")
}
errs := []string{}
for _, res := range results.GetResults() {
if res.GetStatus() != pb.CheckStatus_OK {
errs = append(errs, res.GetFriendlyMessageToUser())
}
}
if len(errs) == 0 {
return nil
}
errsStr := strings.Join(errs, "\n ")
return errors.New(errsStr)
}),
)
return healthcheck.NewCategory(LinkerdVizExtensionCheck, checks, true)
}
// VizDataPlaneCategory returns a healthcheck.Category containing checkers
// to verify the data-plane metrics in prometheus and the tap injection
func (hc *HealthChecker) VizDataPlaneCategory() *healthcheck.Category {
return healthcheck.NewCategory(LinkerdVizExtensionDataPlaneCheck, []healthcheck.Checker{
*healthcheck.NewChecker("data plane namespace exists").
WithHintAnchor("l5d-data-plane-exists").
Fatal().
WithCheck(func(ctx context.Context) error {
if hc.DataPlaneNamespace == "" {
// when checking proxies in all namespaces, this check is a no-op
return nil
}
return hc.CheckNamespace(ctx, hc.DataPlaneNamespace, true)
}),
*healthcheck.NewChecker("prometheus is authorized to scrape data plane pods").
WithHintAnchor("l5d-viz-data-plane-prom-authz").
Warning().
WithCheck(func(ctx context.Context) error {
return hc.checkPromAuthorized(ctx)
}),
*healthcheck.NewChecker("data plane proxy metrics are present in Prometheus").
WithHintAnchor("l5d-data-plane-prom").
Warning().
WithRetryDeadline(hc.RetryDeadline).
WithCheck(func(ctx context.Context) (err error) {
pods, err := hc.getDataPlanePodsFromVizAPI(ctx)
if err != nil {
return err
}
return validateDataPlanePodReporting(pods)
}),
}, true)
}
func (hc *HealthChecker) getDataPlanePodsFromVizAPI(ctx context.Context) ([]*pb.Pod, error) {
req := &pb.ListPodsRequest{}
if hc.DataPlaneNamespace != "" {
req.Selector = &pb.ResourceSelection{
Resource: &pb.Resource{
Namespace: hc.DataPlaneNamespace,
},
}
}
resp, err := hc.VizAPIClient().ListPods(ctx, req)
if err != nil {
return nil, err
}
pods := make([]*pb.Pod, 0)
for _, pod := range resp.GetPods() {
if pod.ControllerNamespace == hc.ControlPlaneNamespace {
pods = append(pods, pod)
}
}
return pods, nil
}
func validateDataPlanePodReporting(pods []*pb.Pod) error {
notInPrometheus := []string{}
for _, p := range pods {
// the `Added` field indicates the pod was found in Prometheus
if !p.Added {
notInPrometheus = append(notInPrometheus, p.Name)
}
}
errMsg := ""
if len(notInPrometheus) > 0 {
errMsg = fmt.Sprintf("Data plane metrics not found for %s.", strings.Join(notInPrometheus, ", "))
}
if errMsg != "" {
return fmt.Errorf(errMsg)
}
return nil
}
func fetchTapCaBundle(ctx context.Context, kubeAPI *k8s.KubernetesAPI) ([]*x509.Certificate, error) {
apiServiceClient, err := apiregistrationv1client.NewForConfig(kubeAPI.Config)
if err != nil {
return nil, err
}
apiService, err := apiServiceClient.APIServices().Get(ctx, linkerdTapAPIServiceName, metav1.GetOptions{})
if err != nil {
return nil, err
}
caBundle, err := tls.DecodePEMCertificates(string(apiService.Spec.CABundle))
if err != nil {
return nil, err
}
return caBundle, nil
}
func (hc *HealthChecker) checkPromAuthorized(ctx context.Context) error {
api := hc.KubeAPIClient()
nses, err := hc.getDataPlaneNamespaces(ctx, api)
if err != nil {
return err
}
unauthorizedPods := []string{}
for _, ns := range nses {
// first, let's see if this namespace has an `allow-scrapes` policy. if
// it does, skip checking its pods --- prometheus will be able to scrape
// them even if they are default-deny.
_, err := api.L5dCrdClient.PolicyV1alpha1().AuthorizationPolicies(ns.GetName()).Get(ctx, "prometheus-scrape", metav1.GetOptions{})
if kerrors.IsNotFound(err) {
// no prometheus-scrape policy exists in this namespace
} else if err != nil {
// something went wrong while talking to the kube API
return fmt.Errorf("could not get AuthorizationPolicies in the %s namespace: %w", ns.GetName(), err)
} else {
// allow-scrapes policy exists in this namespace, don't check the
// pods.
continue
}
pods, err := hc.KubeAPIClient().GetPodsByNamespace(ctx, ns.GetName())
if err != nil {
return fmt.Errorf("could not list pods in the %s namespace: %w", ns.GetName(), err)
}
var nsPrefix string
if ns.GetName() == hc.DataPlaneNamespace {
// if we're only checking one namespace, don't bother appending the
// namespace name to the pod's name in the error output
nsPrefix = ""
} else {
// otherwise, include the namespace name as well as the pod's
// name, since we are checking all namespaces.
nsPrefix = ns.GetName() + "/"
}
for _, pod := range pods {
// rather than checking the value of the pod's
// `config.linkerd.io/default-inbound-policy` annotation, check the
// proxy container's actual env variable. if the cluster-wide
// default inbound policy is `deny`, there won't be an override
// annotation, but the proxy-injector will have set the env variable
// directly.
containers := append(pod.Spec.InitContainers, pod.Spec.Containers...)
for _, c := range containers {
if c.Name == k8s.ProxyContainerName {
for _, env := range c.Env {
if env.Name == "LINKERD2_PROXY_INBOUND_DEFAULT_POLICY" && env.Value == "deny" {
unauthorizedPods = append(unauthorizedPods, fmt.Sprintf("\t* %s%s", nsPrefix, pod.Name))
break
}
}
break
}
}
}
}
if len(unauthorizedPods) > 0 {
podList := strings.Join(unauthorizedPods, "\n")
return fmt.Errorf("prometheus may not be authorized to scrape the following pods:\n%s\n"+
" consider running `linkerd viz allow-scrapes` to authorize prometheus scrapes",
podList)
}
return nil
}
func (hc *HealthChecker) getDataPlaneNamespaces(ctx context.Context, api *k8s.KubernetesAPI) ([]corev1.Namespace, error) {
if hc.DataPlaneNamespace != "" {
ns, err := api.GetNamespace(ctx, hc.DataPlaneNamespace)
if err != nil {
return nil, err
}
return []corev1.Namespace{*ns}, nil
}
nses, err := api.CoreV1().Namespaces().List(ctx, metav1.ListOptions{})
if err != nil {
return nil, err
}
return nses.Items, nil
}
package labels
import (
"strconv"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const (
// VizAnnotationsPrefix is the prefix of all viz-related annotations
VizAnnotationsPrefix = "viz.linkerd.io"
// VizTapEnabled is set by the tap-injector component when tap has been
// enabled on a pod.
VizTapEnabled = VizAnnotationsPrefix + "/tap-enabled"
// VizTapDisabled can be used to disable tap on the injected proxy.
VizTapDisabled = VizAnnotationsPrefix + "/disable-tap"
// VizExternalPrometheus is only set on the namespace by the install
// when a external prometheus is being used
VizExternalPrometheus = VizAnnotationsPrefix + "/external-prometheus"
)
// IsTapEnabled returns true if a pod has an annotation indicating that tap
// is enabled.
func IsTapEnabled(pod *corev1.Pod) bool {
valStr := pod.GetAnnotations()[VizTapEnabled]
if valStr != "" {
valBool, err := strconv.ParseBool(valStr)
if err == nil && valBool {
return true
}
}
return false
}
// IsTapDisabled returns true if a namespace or pod has an annotation for
// explicitly disabling tap
func IsTapDisabled(obj metav1.Object) bool {
valStr := obj.GetAnnotations()[VizTapDisabled]
if valStr != "" {
valBool, err := strconv.ParseBool(valStr)
if err == nil && valBool {
return true
}
}
return false
}