@@ -2754,16 +2754,30 @@ func (c *Client) DeleteToken(ctx context.Context, name string) error {
27542754 return trace .Wrap (err )
27552755}
27562756
2757- // GetNode returns a node by name and namespace.
2757+ // GetNode returns an unscoped node by name and namespace.
2758+ //
2759+ // Deprecated: Use [Client.GetSSHServer] instead, which supports scoped nodes.
2760+ // TODO(williamo): Remove in v20
27582761func (c * Client ) GetNode (ctx context.Context , namespace , name string ) (types.Server , error ) {
2759- resp , err := c .grpc .GetNode (ctx , & types.ResourceInNamespaceRequest {
2760- Name : name ,
2761- Namespace : namespace ,
2762- })
2762+ return c .GetSSHServer (ctx , presencepb.GetSSHServerRequest_builder {Name : name }.Build ())
2763+ }
2764+
2765+ // GetSSHServer returns a scoped or unscoped ssh servers by name.
2766+ func (c * Client ) GetSSHServer (ctx context.Context , req * presencepb.GetSSHServerRequest ) (types.Server , error ) {
2767+ resp , err := c .PresenceServiceClient ().GetSSHServer (ctx , req )
27632768 if err != nil {
2764- return nil , trace .Wrap (err )
2769+ if ! trace .IsNotImplemented (trace .Wrap (err )) {
2770+ return nil , trace .Wrap (err )
2771+ }
2772+ if req .GetScope () != "" {
2773+ return nil , trace .BadParameter ("requesting a scoped node from an outdated Teleport control plane that does not support it" )
2774+ }
2775+ return c .grpc .GetNode (ctx , & types.ResourceInNamespaceRequest {
2776+ Name : req .Name ,
2777+ Namespace : defaults .Namespace ,
2778+ })
27652779 }
2766- return resp , nil
2780+ return resp . GetServer () , nil
27672781}
27682782
27692783// GetNodes returns a complete list of nodes that the user has access to in the given namespace.
@@ -2776,6 +2790,94 @@ func (c *Client) GetNodes(ctx context.Context, namespace string) ([]types.Server
27762790 return servers , trace .Wrap (err )
27772791}
27782792
2793+ // ListSSHServers returns a page of registered ssh servers respecting scope filters.
2794+ func (c * Client ) ListSSHServers (ctx context.Context , req * presencepb.ListSSHServersRequest ) ([]types.Server , string , error ) {
2795+ res , err := c .PresenceServiceClient ().ListSSHServers (ctx , req )
2796+ if err != nil {
2797+ if ! trace .IsNotImplemented (err ) {
2798+ return nil , "" , trace .Wrap (err )
2799+ }
2800+
2801+ // only allow fallback if the request is not expecting results to be scope filtered
2802+ if req .GetScopeFilter ().GetScope () != "" {
2803+ return nil , "" , trace .BadParameter ("requesting list of scoped nodes from an outdated Teleport control plane that does not support it" )
2804+ }
2805+
2806+ return c .listNodesFallback (ctx , int (req .GetPageSize ()), req .GetPageToken ())
2807+ }
2808+
2809+ servers := make ([]types.Server , 0 , len (res .GetServers ()))
2810+ for _ , server := range res .GetServers () {
2811+ servers = append (servers , server )
2812+ }
2813+ return servers , res .GetNextPageToken (), nil
2814+ }
2815+
2816+ func (c * Client ) listNodesFallback (ctx context.Context , pageSize int , pageToken string ) ([]types.Server , string , error ) {
2817+ resp , err := c .ListResources (ctx , proto.ListResourcesRequest {
2818+ ResourceType : types .KindNode ,
2819+ Namespace : defaults .Namespace ,
2820+ Limit : int32 (pageSize ),
2821+ StartKey : pageToken ,
2822+ })
2823+ if err != nil {
2824+ return nil , "" , trace .Wrap (err )
2825+ }
2826+
2827+ servers := make ([]types.Server , 0 , len (resp .Resources ))
2828+ for _ , resource := range resp .Resources {
2829+ server , ok := resource .(types.Server )
2830+ if ! ok {
2831+ return nil , "" , trace .BadParameter ("expected types.Server, got %T" , resource )
2832+ }
2833+ servers = append (servers , server )
2834+ }
2835+ return servers , resp .NextKey , nil
2836+ }
2837+
2838+ // RangeSSHServers returns a sequence of ssh servers filtered by the given
2839+ // [*presencepb.ListSSHServersRequest].
2840+ func (c * Client ) RangeSSHServers (ctx context.Context , req * presencepb.ListSSHServersRequest ) iter.Seq2 [types.Server , error ] {
2841+ if req == nil {
2842+ req = presencepb.ListSSHServersRequest_builder {}.Build ()
2843+ }
2844+
2845+ pageFn := func (ctx context.Context , pageSize int , pageToken string ) ([]types.Server , string , error ) {
2846+ req .SetPageToken (pageToken )
2847+ req .SetPageSize (int32 (pageSize ))
2848+ return c .ListSSHServers (ctx , req )
2849+ }
2850+ return func (yield func (cluster types.Server , err error ) bool ) {
2851+ var fallback bool
2852+ for cluster , err := range clientutils .RangeResources (ctx , req .GetPageToken (), "" , pageFn , types .Server .GetName ) {
2853+ if trace .IsNotImplemented (err ) {
2854+ // if control plane does not support ListNodes, we should try to fallback to the
2855+ // ListResources API
2856+ fallback = true
2857+ break
2858+ }
2859+ if ! yield (cluster , err ) {
2860+ return
2861+ }
2862+ }
2863+ if ! fallback {
2864+ return
2865+ }
2866+ if req .GetScopeFilter ().GetScope () != "" {
2867+ // only allow fallback if the request is not expecting results to be scope filtered
2868+ yield (nil , trace .BadParameter ("requesting range of scoped kube cluster from an outdated Teleport control plane that does not support it" ))
2869+ return
2870+ }
2871+ // fallback iterator
2872+ //nolint:staticcheck // TODO(eriktate): deprecated, to be removed in v20
2873+ for cluster , err := range clientutils .RangeResources (ctx , req .GetPageToken (), "" , c .listNodesFallback , nil ) {
2874+ if ! yield (cluster , err ) {
2875+ return
2876+ }
2877+ }
2878+ }
2879+ }
2880+
27792881// UpsertNode is used by SSH servers to report their presence
27802882// to the auth servers in form of heartbeat expiring after ttl period.
27812883func (c * Client ) UpsertNode (ctx context.Context , node types.Server ) (* types.KeepAlive , error ) {
@@ -2793,19 +2895,40 @@ func (c *Client) UpsertNode(ctx context.Context, node types.Server) (*types.Keep
27932895 return keepAlive , nil
27942896}
27952897
2796- // DeleteNode deletes a node by name and namespace.
2898+ // DeleteNode deletes an unscoped node by name and namespace.
2899+ //
2900+ // Deprecated: Use [Client.DeleteSSHServer] instead, which supports scoped nodes.
2901+ // TODO(williamo): Remove in v20
27972902func (c * Client ) DeleteNode (ctx context.Context , namespace , name string ) error {
27982903 if namespace == "" {
27992904 return trace .BadParameter ("missing parameter namespace" )
28002905 }
28012906 if name == "" {
28022907 return trace .BadParameter ("missing parameter name" )
28032908 }
2804- _ , err := c .grpc .DeleteNode (ctx , & types.ResourceInNamespaceRequest {
2805- Name : name ,
2806- Namespace : namespace ,
2807- })
2808- return trace .Wrap (err )
2909+ return trace .Wrap (c .DeleteSSHServer (ctx , presencepb.DeleteSSHServerRequest_builder {Name : name }.Build ()))
2910+ }
2911+
2912+ // DeleteSSHServer deletes a scoped or unscoped ssh server by name.
2913+ func (c * Client ) DeleteSSHServer (ctx context.Context , req * presencepb.DeleteSSHServerRequest ) error {
2914+ _ , err := c .PresenceServiceClient ().DeleteSSHServer (ctx , req )
2915+ if err != nil {
2916+ if ! trace .IsNotImplemented (trace .Wrap (err )) {
2917+ return trace .Wrap (err )
2918+ }
2919+ if req .GetScope () != "" {
2920+ return trace .BadParameter ("requesting deletion of a scoped node from an outdated Teleport control plane that does not support it" )
2921+ }
2922+
2923+ if _ , err := c .grpc .DeleteNode (ctx , & types.ResourceInNamespaceRequest {
2924+ Namespace : defaults .Namespace ,
2925+ Name : req .Name ,
2926+ }); err != nil {
2927+ return trace .Wrap (err )
2928+ }
2929+
2930+ }
2931+ return nil
28092932}
28102933
28112934// DeleteAllNodes deletes all nodes in a given namespace.
0 commit comments