Skip to content

Commit 373dc94

Browse files
cheina97adamjensenbot
authored andcommitted
feat: liqoctl gateway template check
1 parent d048681 commit 373dc94

File tree

8 files changed

+241
-58
lines changed

8 files changed

+241
-58
lines changed

cmd/liqoctl/cmd/network.go

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ func newNetworkCommand(ctx context.Context, f *factory.Factory) *cobra.Command {
6565

6666
cmd.PersistentFlags().DurationVar(&options.Timeout, "timeout", 120*time.Second, "Timeout for completion")
6767
cmd.PersistentFlags().BoolVar(&options.Wait, "wait", false, "Wait for completion")
68+
cmd.PersistentFlags().BoolVar(&options.SkipValidation, "skip-validation", false, "Skip the validation")
6869

6970
options.LocalFactory.AddFlags(cmd.PersistentFlags(), cmd.RegisterFlagCompletionFunc)
7071
options.RemoteFactory.AddFlags(cmd.PersistentFlags(), cmd.RegisterFlagCompletionFunc)
@@ -139,12 +140,7 @@ func newNetworkConnectCommand(ctx context.Context, options *network.Options) *co
139140
Args: cobra.NoArgs,
140141

141142
Run: func(_ *cobra.Command, _ []string) {
142-
err := options.RunConnect(ctx)
143-
if err != nil {
144-
options.LocalFactory.Printer.CheckErr(
145-
fmt.Errorf("`network connect` failed (error: %w). Issue `network disconnect` to cleanup the environment", err))
146-
}
147-
output.ExitOnErr(err)
143+
output.ExitOnErr(options.RunConnect(ctx))
148144
},
149145
}
150146

@@ -156,15 +152,15 @@ func newNetworkConnectCommand(ctx context.Context, options *network.Options) *co
156152
cmd.Flags().StringVar(&options.ServerTemplateNamespace, "server-template-namespace", "",
157153
"Namespace of the Gateway Server template")
158154
cmd.Flags().Var(options.ServerServiceType, "server-service-type",
159-
fmt.Sprintf("Service type of the Gateway Server. Default: %s."+
155+
fmt.Sprintf("Service type of the Gateway Server service. Default: %s."+
160156
" Note: use ClusterIP only if you know what you are doing and you have a proper network configuration",
161157
forge.DefaultGwServerServiceType))
162-
cmd.Flags().Int32Var(&options.ServerPort, "server-port", forge.DefaultGwServerPort,
163-
fmt.Sprintf("Port of the Gateway Server. Default: %d", forge.DefaultGwServerPort))
164-
cmd.Flags().Int32Var(&options.ServerNodePort, "node-port", 0,
165-
"Force the NodePort of the Gateway Server. Leave empty to let Kubernetes allocate a random NodePort")
166-
cmd.Flags().StringVar(&options.ServerLoadBalancerIP, "load-balancer-ip", "",
167-
"Force LoadBalancer IP of the Gateway Server. Leave empty to use the one provided by the LoadBalancer provider")
158+
cmd.Flags().Int32Var(&options.ServerServicePort, "server-service-port", forge.DefaultGwServerPort,
159+
fmt.Sprintf("Port of the Gateway Server service. Default: %d", forge.DefaultGwServerPort))
160+
cmd.Flags().Int32Var(&options.ServerServiceNodePort, "server-service-nodeport", 0,
161+
"Force the NodePort of the Gateway Server service. Leave empty to let Kubernetes allocate a random NodePort")
162+
cmd.Flags().StringVar(&options.ServerServiceLoadBalancerIP, "server-service-loadbalancerip", "",
163+
"Force LoadBalancer IP of the Gateway Server service. Leave empty to use the one provided by the LoadBalancer provider")
168164

169165
// Client flags
170166
cmd.Flags().StringVar(&options.ClientGatewayType, "client-type", forge.DefaultGwClientType,

cmd/liqoctl/cmd/peer.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ func newPeerCommand(ctx context.Context, f *factory.Factory) *cobra.Command {
7575
}
7676

7777
cmd.PersistentFlags().DurationVar(&options.Timeout, "timeout", 10*time.Minute, "Timeout for peering completion")
78+
cmd.PersistentFlags().BoolVar(&options.SkipValidation, "skip-validation", false, "Skip the validation")
7879

7980
options.LocalFactory.AddFlags(cmd.PersistentFlags(), cmd.RegisterFlagCompletionFunc)
8081
options.RemoteFactory.AddFlags(cmd.PersistentFlags(), cmd.RegisterFlagCompletionFunc)
@@ -90,11 +91,15 @@ func newPeerCommand(ctx context.Context, f *factory.Factory) *cobra.Command {
9091
// Networking flags
9192
cmd.Flags().BoolVar(&options.NetworkingDisabled, "networking-disabled", false, "Disable networking between the two clusters")
9293
cmd.Flags().Var(options.ServerServiceType, "server-service-type",
93-
fmt.Sprintf("Service type of the Gateway Server. Default: %s."+
94+
fmt.Sprintf("Service type of the Gateway Server service. Default: %s."+
9495
" Note: use ClusterIP only if you know what you are doing and you have a proper network configuration",
9596
nwforge.DefaultGwServerServiceType))
96-
cmd.Flags().Int32Var(&options.ServerPort, "server-port", nwforge.DefaultGwServerPort,
97-
fmt.Sprintf("Port of the Gateway Server. Default: %d", nwforge.DefaultGwServerPort))
97+
cmd.Flags().Int32Var(&options.ServerServicePort, "server-service-port", nwforge.DefaultGwServerPort,
98+
fmt.Sprintf("Port of the Gateway Server service. Default: %d", nwforge.DefaultGwServerPort))
99+
cmd.Flags().Int32Var(&options.ServerServiceNodePort, "server-service-nodeport", 0,
100+
"Force the NodePort of the Gateway Server service. Leave empty to let Kubernetes allocate a random NodePort")
101+
cmd.Flags().StringVar(&options.ServerServiceLoadBalancerIP, "server-service-loadbalancerip", "",
102+
"IP of the LoadBalancer for the Gateway Server service")
98103
cmd.Flags().IntVar(&options.MTU, "mtu", nwforge.DefaultMTU,
99104
fmt.Sprintf("MTU of the Gateway server and client. Default: %d", nwforge.DefaultMTU))
100105

pkg/liqoctl/factory/factory.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/spf13/pflag"
2323
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
2424
"k8s.io/cli-runtime/pkg/genericclioptions"
25+
"k8s.io/client-go/dynamic"
2526
"k8s.io/client-go/kubernetes"
2627
"k8s.io/client-go/rest"
2728
cmdutil "k8s.io/kubectl/pkg/cmd/util"
@@ -58,6 +59,8 @@ type Factory struct {
5859

5960
// Printer is the object used to output messages in the appropriate format.
6061
Printer *output.Printer
62+
// PrinterGlobal is the object used to output messages in the appropriate format. It is not scoped to local or remote cluster.
63+
PrinterGlobal *output.Printer
6164
// SkipConfirm determines whether to skip confirmations.
6265
SkipConfirm bool
6366
// Whether to add a scope to the printer (i.e., local/remote).
@@ -79,6 +82,9 @@ type Factory struct {
7982
// kubeClient is a Kubernetes clientset for interacting with the base Kubernetes APIs.
8083
KubeClient kubernetes.Interface
8184

85+
// DynCLient
86+
DynClient *dynamic.DynamicClient
87+
8288
helmClient helm.Client
8389
}
8490

@@ -209,6 +215,8 @@ func (f *Factory) Initialize(opts ...Options) (err error) {
209215
f.Printer = output.NewLocalPrinter(o.scoped, verbose)
210216
}
211217

218+
f.PrinterGlobal = output.NewGlobalPrinter(o.scoped, verbose)
219+
212220
if f.Namespace == "" {
213221
f.Namespace, _, err = f.factory.ToRawKubeConfigLoader().Namespace()
214222
if err != nil {
@@ -232,6 +240,11 @@ func (f *Factory) Initialize(opts ...Options) (err error) {
232240
return err
233241
}
234242

243+
f.DynClient, err = dynamic.NewForConfig(f.RESTConfig)
244+
if err != nil {
245+
return err
246+
}
247+
235248
// Leverage the REST mapper retrieved from the factory, to defer the loading of the mappings until the first API
236249
// request is made. This prevents errors in case of invalid kubeconfigs, if no interaction is required.
237250
f.CRClient, err = client.New(f.RESTConfig, client.Options{Mapper: restMapper})

pkg/liqoctl/network/cluster.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
corev1 "k8s.io/api/core/v1"
2222
apierrors "k8s.io/apimachinery/pkg/api/errors"
2323
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2425
"k8s.io/apimachinery/pkg/types"
2526
"sigs.k8s.io/controller-runtime/pkg/client"
2627
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
@@ -29,6 +30,7 @@ import (
2930
networkingv1beta1 "github.com/liqotech/liqo/apis/networking/v1beta1"
3031
"github.com/liqotech/liqo/pkg/consts"
3132
gwforge "github.com/liqotech/liqo/pkg/gateway/forge"
33+
enutils "github.com/liqotech/liqo/pkg/liqo-controller-manager/networking/external-network/utils"
3234
"github.com/liqotech/liqo/pkg/liqo-controller-manager/networking/forge"
3335
networkingutils "github.com/liqotech/liqo/pkg/liqo-controller-manager/networking/utils"
3436
"github.com/liqotech/liqo/pkg/liqoctl/factory"
@@ -37,6 +39,7 @@ import (
3739
tenantnamespace "github.com/liqotech/liqo/pkg/tenantNamespace"
3840
liqoutils "github.com/liqotech/liqo/pkg/utils"
3941
"github.com/liqotech/liqo/pkg/utils/getters"
42+
"github.com/liqotech/liqo/pkg/utils/maps"
4043
)
4144

4245
// Cluster contains the information about a cluster.
@@ -194,6 +197,130 @@ func (c *Cluster) SetupConfiguration(ctx context.Context, conf *networkingv1beta
194197
return nil
195198
}
196199

200+
// CheckTemplateGwClient checks if the GatewayClient template is correctly set up.
201+
func (c *Cluster) CheckTemplateGwClient(ctx context.Context, opts *Options) error {
202+
templateName := opts.ClientTemplateName
203+
templateNamespace := opts.ClientTemplateNamespace
204+
templateGVR := opts.ClientGatewayType
205+
206+
s := c.local.Printer.StartSpinner(fmt.Sprintf("Checking gateway client template \"%s/%s\"",
207+
templateName, templateNamespace))
208+
209+
_, err := c.checkTemplate(ctx, templateName, templateNamespace, templateGVR)
210+
if err != nil {
211+
s.Fail(fmt.Sprintf("An error occurred while checking gateway client template \"%s/%s\": %v",
212+
templateName, templateNamespace, output.PrettyErr(err)))
213+
return err
214+
}
215+
216+
s.Success(fmt.Sprintf("Gateway client template \"%s/%s\" correctly checked",
217+
templateName, templateNamespace))
218+
return nil
219+
}
220+
221+
// CheckTemplateGwServer checks if the GatewayServer template is correctly set up.
222+
func (c *Cluster) CheckTemplateGwServer(ctx context.Context, opts *Options) error {
223+
templateName := opts.ServerTemplateName
224+
templateNamespace := opts.ServerTemplateNamespace
225+
templateGVR := opts.ServerGatewayType
226+
227+
s := c.local.Printer.StartSpinner(fmt.Sprintf("Checking gateway server template \"%s/%s\"",
228+
templateName, templateNamespace))
229+
230+
template, err := c.checkTemplate(ctx, templateName, templateNamespace, templateGVR)
231+
if err != nil {
232+
s.Fail(fmt.Sprintf("An error occurred while checking gateway server template \"%s/%s\": %v",
233+
templateName, templateNamespace, output.PrettyErr(err)))
234+
return err
235+
}
236+
237+
if err := c.checkTemplateServerService(template, opts); err != nil {
238+
s.Fail(fmt.Sprintf("An error occurred while checking gateway server template \"%s/%s\": %v",
239+
templateName, templateNamespace, output.PrettyErr(err)))
240+
return err
241+
}
242+
243+
s.Success(fmt.Sprintf("Gateway server template \"%s/%s\" correctly checked",
244+
templateName, templateNamespace))
245+
return nil
246+
}
247+
248+
func (c *Cluster) checkTemplate(ctx context.Context, templateName, templateNamespace, templateGvr string) (*unstructured.Unstructured, error) {
249+
// Server Template Reference
250+
gvr, err := enutils.ParseGroupVersionResource(templateGvr)
251+
if err != nil {
252+
return nil, err
253+
}
254+
255+
template, err := c.local.DynClient.Resource(gvr).Namespace(templateNamespace).Get(ctx, templateName, metav1.GetOptions{})
256+
if err != nil {
257+
return nil, err
258+
}
259+
260+
return template, nil
261+
}
262+
263+
// checkTemplateServerService checks if the GatewayServer service template is correctly set up.
264+
func (c *Cluster) checkTemplateServerService(template *unstructured.Unstructured, opts *Options) error {
265+
switch corev1.ServiceType(opts.ServerServiceType.Value) {
266+
case corev1.ServiceTypeClusterIP:
267+
return c.checkTemplateServerServiceClusterIP(template, opts)
268+
case corev1.ServiceTypeNodePort:
269+
return c.checkTemplateServerServiceNodePort(template, opts)
270+
case corev1.ServiceTypeLoadBalancer:
271+
return c.checkTemplateServerServiceLoadBalancer(template, opts)
272+
case corev1.ServiceTypeExternalName:
273+
return fmt.Errorf("externalName service type not supported")
274+
}
275+
return nil
276+
}
277+
278+
func (c *Cluster) checkTemplateServerServiceClusterIP(_ *unstructured.Unstructured, _ *Options) error {
279+
return nil
280+
}
281+
func (c *Cluster) checkTemplateServerServiceNodePort(template *unstructured.Unstructured, opts *Options) error {
282+
if opts.ServerServiceNodePort == 0 {
283+
return nil
284+
}
285+
286+
path := "spec.template.spec.service.spec.ports"
287+
templateServicePorts, err := maps.GetNestedField(template.Object, path)
288+
if err != nil {
289+
return fmt.Errorf("unable to get %s of the server template", path)
290+
}
291+
292+
templateServicePortsSlice, ok := templateServicePorts.([]interface{})
293+
if !ok {
294+
return fmt.Errorf("unable to cast %s to []interface{}", path)
295+
}
296+
297+
port, ok := templateServicePortsSlice[0].(map[string]interface{})
298+
if !ok {
299+
return fmt.Errorf("unable to cast %s to map[string]interface{}", path)
300+
}
301+
302+
_, err = maps.GetNestedField(port, "nodePort")
303+
if err != nil {
304+
return fmt.Errorf("unable to get spec.template.spec.service.spec.ports[0].nodePort int the server template, " +
305+
"since you specified the flag \"--server-service-nodeport\" you need to add the \"nodePort\" field in the template")
306+
}
307+
308+
return nil
309+
}
310+
func (c *Cluster) checkTemplateServerServiceLoadBalancer(template *unstructured.Unstructured, opts *Options) error {
311+
if opts.ServerServiceLoadBalancerIP == "" {
312+
return nil
313+
}
314+
315+
path := "spec.template.spec.service.spec.loadBalancerIP"
316+
_, err := maps.GetNestedField(template.Object, path)
317+
if err != nil {
318+
return fmt.Errorf("unable to get %s of the server template, "+
319+
"since you specified the flag \"--server-service-loadbalancerip\" you need to add the \"loadBalancerIP\" field in the template", path)
320+
}
321+
return nil
322+
}
323+
197324
// CheckNetworkInitialized checks if the network is initialized correctly.
198325
func (c *Cluster) CheckNetworkInitialized(ctx context.Context, remoteClusterID liqov1beta1.ClusterID) error {
199326
s := c.local.Printer.StartSpinner("Checking network is initialized correctly")

pkg/liqoctl/network/handler.go

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,17 @@ type Options struct {
3535
LocalFactory *factory.Factory
3636
RemoteFactory *factory.Factory
3737

38-
Timeout time.Duration
39-
Wait bool
40-
41-
ServerGatewayType string
42-
ServerTemplateName string
43-
ServerTemplateNamespace string
44-
ServerServiceType *argsutils.StringEnum
45-
ServerPort int32
46-
ServerNodePort int32
47-
ServerLoadBalancerIP string
38+
Timeout time.Duration
39+
Wait bool
40+
SkipValidation bool
41+
42+
ServerGatewayType string
43+
ServerTemplateName string
44+
ServerTemplateNamespace string
45+
ServerServiceType *argsutils.StringEnum
46+
ServerServicePort int32
47+
ServerServiceNodePort int32
48+
ServerServiceLoadBalancerIP string
4849

4950
ClientGatewayType string
5051
ClientTemplateName string
@@ -153,6 +154,14 @@ func (o *Options) RunConnect(ctx context.Context) error {
153154
ctx, cancel := context.WithTimeout(ctx, o.Timeout)
154155
defer cancel()
155156

157+
if o.ServerTemplateNamespace == "" {
158+
o.ServerTemplateNamespace = o.RemoteFactory.LiqoNamespace
159+
}
160+
161+
if o.ClientTemplateNamespace == "" {
162+
o.ClientTemplateNamespace = o.LocalFactory.LiqoNamespace
163+
}
164+
156165
// Create and initialize cluster 1.
157166
cluster1, err := NewCluster(ctx, o.LocalFactory, o.RemoteFactory, true)
158167
if err != nil {
@@ -165,6 +174,18 @@ func (o *Options) RunConnect(ctx context.Context) error {
165174
return err
166175
}
167176

177+
if !o.SkipValidation {
178+
// Check if the Templates exists and is valid on cluster 2
179+
if err := cluster2.CheckTemplateGwServer(ctx, o); err != nil {
180+
return err
181+
}
182+
183+
// Check if the Templates exists and is valid on cluster 1
184+
if err := cluster1.CheckTemplateGwClient(ctx, o); err != nil {
185+
return err
186+
}
187+
}
188+
168189
// Check if the Networking is initialized on cluster 1
169190
if err := cluster1.CheckNetworkInitialized(ctx, cluster2.localClusterID); err != nil {
170191
return err
@@ -319,10 +340,6 @@ func (o *Options) RunDisconnect(ctx context.Context, cluster1, cluster2 *Cluster
319340
}
320341

321342
func (o *Options) newGatewayServerForgeOptions(kubeClient kubernetes.Interface, remoteClusterID liqov1beta1.ClusterID) *forge.GwServerOptions {
322-
if o.ServerTemplateNamespace == "" {
323-
o.ServerTemplateNamespace = o.RemoteFactory.LiqoNamespace
324-
}
325-
326343
return &forge.GwServerOptions{
327344
KubeClient: kubeClient,
328345
RemoteClusterID: remoteClusterID,
@@ -331,18 +348,14 @@ func (o *Options) newGatewayServerForgeOptions(kubeClient kubernetes.Interface,
331348
TemplateNamespace: o.ServerTemplateNamespace,
332349
ServiceType: corev1.ServiceType(o.ServerServiceType.Value),
333350
MTU: o.MTU,
334-
Port: o.ServerPort,
335-
NodePort: ptr.To(o.ServerNodePort),
336-
LoadBalancerIP: ptr.To(o.ServerLoadBalancerIP),
351+
Port: o.ServerServicePort,
352+
NodePort: ptr.To(o.ServerServiceNodePort),
353+
LoadBalancerIP: ptr.To(o.ServerServiceLoadBalancerIP),
337354
}
338355
}
339356

340357
func (o *Options) newGatewayClientForgeOptions(kubeClient kubernetes.Interface, remoteClusterID liqov1beta1.ClusterID,
341358
serverEndpoint *networkingv1beta1.EndpointStatus) *forge.GwClientOptions {
342-
if o.ClientTemplateNamespace == "" {
343-
o.ClientTemplateNamespace = o.LocalFactory.LiqoNamespace
344-
}
345-
346359
return &forge.GwClientOptions{
347360
KubeClient: kubeClient,
348361
RemoteClusterID: remoteClusterID,

pkg/liqoctl/output/output.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,11 @@ func NewRemotePrinter(scoped, verbose bool) *Printer {
264264
return newPrinter(remoteClusterName, remoteClusterColor, scoped, verbose)
265265
}
266266

267+
// NewGlobalPrinter returns a new printer referring to the global scope.
268+
func NewGlobalPrinter(scoped, verbose bool) *Printer {
269+
return newPrinter("global", pterm.FgDefault, scoped, verbose)
270+
}
271+
267272
func newPrinter(scope string, color pterm.Color, scoped, verbose bool) *Printer {
268273
generic := &pterm.PrefixPrinter{MessageStyle: pterm.NewStyle(pterm.FgDefault)}
269274

0 commit comments

Comments
 (0)