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 }