github.com/webmeshproj/webmesh-cni@v0.0.27/internal/types/client.go (about)

     1  /*
     2  Copyright 2023 Avi Zimmerman <avi.zimmerman@gmail.com>.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package types
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"time"
    23  
    24  	"github.com/containernetworking/cni/pkg/skel"
    25  	storagev1 "github.com/webmeshproj/storage-provider-k8s/api/storage/v1"
    26  	corev1 "k8s.io/api/core/v1"
    27  	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/runtime"
    30  	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
    31  	"k8s.io/client-go/rest"
    32  	"k8s.io/client-go/tools/clientcmd"
    33  	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
    34  	"sigs.k8s.io/controller-runtime/pkg/client"
    35  
    36  	meshcniv1 "github.com/webmeshproj/webmesh-cni/api/v1"
    37  )
    38  
    39  // Client is the client for the CNI plugin.
    40  type Client struct {
    41  	client.Client
    42  	conf *NetConf
    43  }
    44  
    45  // ClientConfig is the configuration for the CNI client.
    46  type ClientConfig struct {
    47  	NetConf    *NetConf
    48  	RestConfig *rest.Config
    49  }
    50  
    51  // SchemeBuilders is a list of scheme builders to use for webmesh-cni clients.
    52  var SchemeBuilders = []func(*runtime.Scheme) error{
    53  	clientgoscheme.AddToScheme,
    54  	apiextensions.AddToScheme,
    55  	storagev1.AddToScheme,
    56  	meshcniv1.AddToScheme,
    57  }
    58  
    59  // NewClientForConfig creates a new client from the given configuration.
    60  func NewClientForConfig(conf ClientConfig) (*Client, error) {
    61  	client, err := NewRawClientForConfig(conf.RestConfig)
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  	return &Client{
    66  		Client: client,
    67  		conf:   conf.NetConf,
    68  	}, nil
    69  }
    70  
    71  // NewRawClientForConfig creates a new raw client from the given configuration.
    72  func NewRawClientForConfig(conf *rest.Config) (client.Client, error) {
    73  	scheme := runtime.NewScheme()
    74  	for _, add := range SchemeBuilders {
    75  		if err := add(scheme); err != nil {
    76  			return nil, fmt.Errorf("failed to add scheme: %w", err)
    77  		}
    78  	}
    79  	return client.New(conf, client.Options{
    80  		Scheme: scheme,
    81  		Cache: &client.CacheOptions{
    82  			DisableFor: append(
    83  				storagev1.CustomObjects,
    84  				&meshcniv1.PeerContainer{},
    85  				&corev1.Secret{},
    86  				&corev1.ConfigMap{},
    87  			),
    88  		},
    89  	})
    90  }
    91  
    92  // NewRestConfigFromBytes creates a new rest config from the given kubeconfig bytes.
    93  func NewRestConfigFromBytes(kubeconfig []byte) (*rest.Config, error) {
    94  	return clientcmd.BuildConfigFromKubeconfigGetter("", func() (*clientcmdapi.Config, error) {
    95  		conf, err := clientcmd.Load(kubeconfig)
    96  		if err != nil {
    97  			return nil, fmt.Errorf("failed to load kubeconfig from file: %w", err)
    98  		}
    99  		return conf, nil
   100  	})
   101  }
   102  
   103  // Ping will make sure the client can contact the API server using
   104  // the given timeout.
   105  func (c *Client) Ping(timeout time.Duration) error {
   106  	ctx, cancel := context.WithTimeout(context.Background(), timeout)
   107  	defer cancel()
   108  	// Try to list peer containers from the API server.
   109  	err := c.Client.List(ctx, &meshcniv1.PeerContainerList{}, &client.ListOptions{
   110  		Limit: 1,
   111  	})
   112  	if err != nil {
   113  		return fmt.Errorf("failed to list peer containers: %w", err)
   114  	}
   115  	return nil
   116  }
   117  
   118  // EnsureContainer attempts to retrieve the peer container for the given args.
   119  // If it does not exist, it will create it.
   120  func (c *Client) EnsureContainer(ctx context.Context, args *skel.CmdArgs) error {
   121  	_, err := c.GetPeerContainer(ctx, args)
   122  	if err != nil {
   123  		if IsPeerContainerNotFound(err) {
   124  			return c.CreatePeerContainer(ctx, args)
   125  		}
   126  		return err
   127  	}
   128  	return nil
   129  }
   130  
   131  // GetPeerContainer attempts to retrieve the peer container for the given args.
   132  func (c *Client) GetPeerContainer(ctx context.Context, args *skel.CmdArgs) (*meshcniv1.PeerContainer, error) {
   133  	var container meshcniv1.PeerContainer
   134  	err := c.Client.Get(ctx, c.conf.ObjectKeyFromArgs(args), &container)
   135  	if err != nil {
   136  		if client.IgnoreNotFound(err) == nil {
   137  			return nil, fmt.Errorf("%w: %v", ErrPeerContainerNotFound, err)
   138  		}
   139  		return nil, fmt.Errorf("failed to get peer container: %w", err)
   140  	}
   141  	container.TypeMeta = metav1.TypeMeta{
   142  		Kind:       "PeerContainer",
   143  		APIVersion: meshcniv1.GroupVersion.String(),
   144  	}
   145  	return &container, nil
   146  }
   147  
   148  // CreatePeerContainer attempts to create the peer container for the given args.
   149  func (c *Client) CreatePeerContainer(ctx context.Context, args *skel.CmdArgs) error {
   150  	container := c.conf.ContainerFromArgs(args)
   151  	err := c.Patch(ctx, &container, client.Apply, client.ForceOwnership, client.FieldOwner(meshcniv1.FieldOwner))
   152  	if err != nil {
   153  		return fmt.Errorf("failed to apply peer container: %w", err)
   154  	}
   155  	return nil
   156  }
   157  
   158  // DeletePeerContainer attempts to delete the peer container for the given args.
   159  func (c *Client) DeletePeerContainer(ctx context.Context, args *skel.CmdArgs) error {
   160  	container := c.conf.ContainerFromArgs(args)
   161  	err := c.Delete(ctx, &container)
   162  	if err != nil && client.IgnoreNotFound(err) != nil {
   163  		return fmt.Errorf("failed to delete peer container: %w", err)
   164  	}
   165  	return nil
   166  }
   167  
   168  // WaitForRunning is a helper function that waits for the container to be running.
   169  func (c *Client) WaitForRunning(ctx context.Context, args *skel.CmdArgs) (*meshcniv1.PeerContainer, error) {
   170  	return c.WaitForStatus(ctx, args, meshcniv1.InterfaceStatusRunning)
   171  }
   172  
   173  // WaitForStatus is a helper function that waits for the given status to be true on the container
   174  // for the given args. The status is returned if it is true before the timeout.
   175  func (c *Client) WaitForStatus(ctx context.Context, args *skel.CmdArgs, status meshcniv1.InterfaceStatus) (*meshcniv1.PeerContainer, error) {
   176  	// Do a quick check to see if the container is already in the desired state.
   177  	container, err := c.GetPeerContainer(ctx, args)
   178  	if err != nil {
   179  		if !IsPeerContainerNotFound(err) {
   180  			return nil, err
   181  		}
   182  	} else if err == nil && container.Status.InterfaceStatus == status {
   183  		return container, nil
   184  	}
   185  	for {
   186  		select {
   187  		case <-ctx.Done():
   188  			return nil, ctx.Err()
   189  		case <-time.After(time.Second):
   190  			container, err := c.GetPeerContainer(ctx, args)
   191  			if err != nil && !IsPeerContainerNotFound(err) {
   192  				return nil, err
   193  			} else if err == nil && container.Status.InterfaceStatus == status {
   194  				return container, nil
   195  			}
   196  		}
   197  	}
   198  }