diff --git a/cmd/liqoctl/cmd/network.go b/cmd/liqoctl/cmd/network.go index 73d1b401e7..286c80cfbe 100644 --- a/cmd/liqoctl/cmd/network.go +++ b/cmd/liqoctl/cmd/network.go @@ -16,67 +16,126 @@ package cmd import ( "context" + "fmt" "time" "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/util/runtime" "github.com/liqotech/liqo/pkg/liqoctl/completion" "github.com/liqotech/liqo/pkg/liqoctl/factory" "github.com/liqotech/liqo/pkg/liqoctl/network" "github.com/liqotech/liqo/pkg/liqoctl/output" + "github.com/liqotech/liqo/pkg/liqoctl/rest/gatewayclient" + "github.com/liqotech/liqo/pkg/liqoctl/rest/gatewayserver" ) const liqoctlNetworkLongHelp = `Manage liqo networking.` const liqoctlNetworkInitLongHelp = `Initialize the liqo networking between two clusters.` +const liqoctlNetworConnectLongHelp = `Connect two clusters using liqo networking. + +Run this command after inizialiting the network using the *network init* command.` + func newNetworkCommand(ctx context.Context, f *factory.Factory) *cobra.Command { - options := &network.Options{LocalFactory: f} + options := network.NewOptions(f) + options.RemoteFactory = factory.NewForRemote() + cmd := &cobra.Command{ Use: "network", Short: "Manage liqo networking", Long: WithTemplate(liqoctlNetworkLongHelp), Args: cobra.NoArgs, + + PersistentPreRun: func(cmd *cobra.Command, args []string) { + twoClustersPersistentPreRun(cmd, options.LocalFactory, options.RemoteFactory, factory.WithScopedPrinter) + }, } cmd.PersistentFlags().DurationVar(&options.Timeout, "timeout", 120*time.Second, "Timeout for completion") cmd.PersistentFlags().BoolVar(&options.Wait, "wait", false, "Wait for completion") + options.LocalFactory.AddFlags(cmd.PersistentFlags(), cmd.RegisterFlagCompletionFunc) + options.RemoteFactory.AddFlags(cmd.PersistentFlags(), cmd.RegisterFlagCompletionFunc) + + options.LocalFactory.AddNamespaceFlag(cmd.PersistentFlags()) + options.RemoteFactory.AddNamespaceFlag(cmd.PersistentFlags()) + + options.LocalFactory.AddLiqoNamespaceFlag(cmd.PersistentFlags()) + options.RemoteFactory.AddLiqoNamespaceFlag(cmd.PersistentFlags()) + + options.LocalFactory.Printer.CheckErr(cmd.RegisterFlagCompletionFunc("namespace", + completion.Namespaces(ctx, options.LocalFactory, completion.NoLimit))) + options.LocalFactory.Printer.CheckErr(cmd.RegisterFlagCompletionFunc("remote-namespace", + completion.Namespaces(ctx, options.RemoteFactory, completion.NoLimit))) + options.LocalFactory.Printer.CheckErr(cmd.RegisterFlagCompletionFunc("liqo-namespace", + completion.Namespaces(ctx, options.LocalFactory, completion.NoLimit))) + options.LocalFactory.Printer.CheckErr(cmd.RegisterFlagCompletionFunc("remote-liqo-namespace", + completion.Namespaces(ctx, options.RemoteFactory, completion.NoLimit))) + cmd.AddCommand(newNetworkInitCommand(ctx, options)) + cmd.AddCommand(newNetworkConnectCommand(ctx, options)) + return cmd } func newNetworkInitCommand(ctx context.Context, options *network.Options) *cobra.Command { - options.RemoteFactory = factory.NewForRemote() - cmd := &cobra.Command{ Use: "init", Short: "Initialize the liqo networking between two clusters", Long: WithTemplate(liqoctlNetworkInitLongHelp), Args: cobra.NoArgs, - PersistentPreRun: func(cmd *cobra.Command, args []string) { - twoClustersPersistentPreRun(cmd, options.LocalFactory, options.RemoteFactory, factory.WithScopedPrinter) - }, - Run: func(cmd *cobra.Command, args []string) { output.ExitOnErr(options.RunInit(ctx)) }, } - options.LocalFactory.AddFlags(cmd.Flags(), cmd.RegisterFlagCompletionFunc) - options.RemoteFactory.AddFlags(cmd.Flags(), cmd.RegisterFlagCompletionFunc) + return cmd +} - options.LocalFactory.AddNamespaceFlag(cmd.Flags()) - options.RemoteFactory.AddNamespaceFlag(cmd.Flags()) +func newNetworkConnectCommand(ctx context.Context, options *network.Options) *cobra.Command { + cmd := &cobra.Command{ + Use: "connect", + Short: "Connect two clusters using liqo networking", + Long: WithTemplate(liqoctlNetworConnectLongHelp), + Args: cobra.NoArgs, - options.LocalFactory.AddLiqoNamespaceFlag(cmd.Flags()) - options.RemoteFactory.AddLiqoNamespaceFlag(cmd.Flags()) + Run: func(cmd *cobra.Command, args []string) { + output.ExitOnErr(options.RunConnect(ctx)) + }, + } - options.LocalFactory.Printer.CheckErr(cmd.RegisterFlagCompletionFunc("namespace", - completion.Namespaces(ctx, options.LocalFactory, completion.NoLimit))) - options.LocalFactory.Printer.CheckErr(cmd.RegisterFlagCompletionFunc("remote-namespace", - completion.Namespaces(ctx, options.RemoteFactory, completion.NoLimit))) + // Server flags + cmd.Flags().StringVar(&options.ServerGatewayType, "server-type", gatewayserver.DefaultGatewayType, + "Type of Gateway Server. Leave empty to use default Liqo implementation of WireGuard") + cmd.Flags().StringVar(&options.ServerTemplateName, "server-template-name", gatewayserver.DefaultTemplateName, + "Name of the Gateway Server template") + cmd.Flags().StringVar(&options.ServerTemplateNamespace, "server-template-namespace", gatewayserver.DefaultTemplateNamespace, + "Namespace of the Gateway Server template") + cmd.Flags().Var(options.ServerServiceType, "server-service-type", + fmt.Sprintf("Service type of the Gateway Server. Default: %s", gatewayserver.DefaultServiceType)) + cmd.Flags().Int32Var(&options.ServerPort, "server-port", gatewayserver.DefaultPort, + fmt.Sprintf("Port of the Gateway Server. Default: %d", gatewayserver.DefaultPort)) + cmd.Flags().IntVar(&options.ServerMTU, "server-mtu", gatewayserver.DefaultMTU, + fmt.Sprintf("MTU of the Gateway Server. Default: %d", gatewayserver.DefaultMTU)) + + // Client flags + cmd.Flags().StringVar(&options.ClientGatewayType, "client-type", gatewayclient.DefaultGatewayType, + "Type of Gateway Client. Leave empty to use default Liqo implementation of WireGuard") + cmd.Flags().StringVar(&options.ClientTemplateName, "client-template-name", gatewayclient.DefaultTemplateName, + "Name of the Gateway Client template") + cmd.Flags().StringVar(&options.ClientTemplateNamespace, "client-template-namespace", gatewayclient.DefaultTemplateNamespace, + "Namespace of the Gateway Client template") + cmd.Flags().IntVar(&options.ClientMTU, "client-mtu", gatewayclient.DefaultMTU, + fmt.Sprintf("MTU of the Gateway Client. Default: %d", gatewayclient.DefaultMTU)) + + // Common flags + cmd.Flags().BoolVar(&options.DisableSharingKeys, "disable-sharing-keys", false, "Disable the sharing of public keys between the two clusters") + cmd.Flags().BoolVar(&options.Proxy, "proxy", gatewayserver.DefaultProxy, "Enable proxy for the Gateway Server") + + runtime.Must(cmd.RegisterFlagCompletionFunc("server-service-type", completion.Enumeration(options.ServerServiceType.Allowed))) return cmd } diff --git a/cmd/liqoctl/cmd/root.go b/cmd/liqoctl/cmd/root.go index 17dd1e5486..d8f9d298d1 100644 --- a/cmd/liqoctl/cmd/root.go +++ b/cmd/liqoctl/cmd/root.go @@ -36,6 +36,9 @@ import ( "github.com/liqotech/liqo/pkg/liqoctl/get" "github.com/liqotech/liqo/pkg/liqoctl/rest" "github.com/liqotech/liqo/pkg/liqoctl/rest/configuration" + "github.com/liqotech/liqo/pkg/liqoctl/rest/gatewayclient" + "github.com/liqotech/liqo/pkg/liqoctl/rest/gatewayserver" + "github.com/liqotech/liqo/pkg/liqoctl/rest/publickey" "github.com/liqotech/liqo/pkg/liqoctl/rest/virtualnode" ) @@ -44,6 +47,9 @@ var liqoctl string var liqoResources = []rest.APIProvider{ virtualnode.VirtualNode, configuration.Configuration, + gatewayserver.GatewayServer, + gatewayclient.GatewayClient, + publickey.PublicKey, } func init() { diff --git a/pkg/liqo-controller-manager/external-network/utils/getters.go b/pkg/liqo-controller-manager/external-network/utils/getters.go index 96b6bfefcb..0c1f06774f 100644 --- a/pkg/liqo-controller-manager/external-network/utils/getters.go +++ b/pkg/liqo-controller-manager/external-network/utils/getters.go @@ -21,6 +21,7 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes" networkingv1alpha1 "github.com/liqotech/liqo/apis/networking/v1alpha1" ) @@ -124,3 +125,19 @@ func KindToResource(kind string) string { // lowercased and pluralized return strings.ToLower(kind) + "s" } + +// ResourceToKind returns the kind name for a given resource. +func ResourceToKind(gvr schema.GroupVersionResource, kubeClient kubernetes.Interface) (string, error) { + res, err := kubeClient.Discovery().ServerResourcesForGroupVersion(gvr.GroupVersion().String()) + if err != nil { + return "", err + } + + for i := range res.APIResources { + if gvr.Resource == KindToResource(res.APIResources[i].Kind) { + return res.APIResources[i].Kind, nil + } + } + + return "", fmt.Errorf("unable to find Kind name associated to resources %q", gvr.Resource) +} diff --git a/pkg/liqo-controller-manager/external-network/wireguard/utils.go b/pkg/liqo-controller-manager/external-network/wireguard/utils.go index e30e9a14d5..133ac53ee5 100644 --- a/pkg/liqo-controller-manager/external-network/wireguard/utils.go +++ b/pkg/liqo-controller-manager/external-network/wireguard/utils.go @@ -28,6 +28,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/predicate" "github.com/liqotech/liqo/pkg/consts" + tunnel "github.com/liqotech/liqo/pkg/gateway/tunnel/wireguard" liqolabels "github.com/liqotech/liqo/pkg/utils/labels" ) @@ -51,17 +52,12 @@ func wireGuardSecretEnquerer(_ context.Context, obj client.Object) []ctrl.Reques { NamespacedName: types.NamespacedName{ Namespace: secret.Namespace, - Name: mapSecretToWireGuardResource(secret.Name), + Name: tunnel.GenerateResourceName(secret.Name), }, }, } } -// TODO:: use generic map function after merge. -func mapSecretToWireGuardResource(secretName string) string { - return secretName -} - func getWireGuardSecret(ctx context.Context, cl client.Client, wgObj metav1.Object) (*corev1.Secret, error) { wgObjNsName := types.NamespacedName{Name: wgObj.GetName(), Namespace: wgObj.GetNamespace()} diff --git a/pkg/liqoctl/completion/completion.go b/pkg/liqoctl/completion/completion.go index c4bc37be6a..5cf9b354ca 100644 --- a/pkg/liqoctl/completion/completion.go +++ b/pkg/liqoctl/completion/completion.go @@ -24,6 +24,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" discoveryv1alpha1 "github.com/liqotech/liqo/apis/discovery/v1alpha1" + networkingv1alpha1 "github.com/liqotech/liqo/apis/networking/v1alpha1" offloadingv1alpha1 "github.com/liqotech/liqo/apis/offloading/v1alpha1" virtualkubeletv1alpha1 "github.com/liqotech/liqo/apis/virtualkubelet/v1alpha1" identitymanager "github.com/liqotech/liqo/pkg/identityManager" @@ -281,3 +282,29 @@ func PVCs(ctx context.Context, f *factory.Factory, argsLimit int) FnType { return common(ctx, f, argsLimit, retriever) } + +// Gateway returns a function to autocomplete Gateway (server or client) names. +func Gateway(ctx context.Context, f *factory.Factory, argsLimit int) FnType { + retriever := func(ctx context.Context, f *factory.Factory) ([]string, error) { + var names []string + + var gwServers networkingv1alpha1.GatewayServerList + if err := f.CRClient.List(ctx, &gwServers, client.InNamespace(f.Namespace)); err != nil { + return nil, err + } + for i := range gwServers.Items { + names = append(names, gwServers.Items[i].Name) + } + + var gwClients networkingv1alpha1.GatewayClientList + if err := f.CRClient.List(ctx, &gwClients, client.InNamespace(f.Namespace)); err != nil { + return nil, err + } + for i := range gwClients.Items { + names = append(names, gwClients.Items[i].Name) + } + return names, nil + } + + return common(ctx, f, argsLimit, retriever) +} diff --git a/pkg/liqoctl/network/cluster.go b/pkg/liqoctl/network/cluster.go index e5b88bed7b..da51084ea8 100644 --- a/pkg/liqoctl/network/cluster.go +++ b/pkg/liqoctl/network/cluster.go @@ -27,6 +27,9 @@ import ( "github.com/liqotech/liqo/pkg/liqoctl/factory" "github.com/liqotech/liqo/pkg/liqoctl/output" "github.com/liqotech/liqo/pkg/liqoctl/rest/configuration" + "github.com/liqotech/liqo/pkg/liqoctl/rest/gatewayclient" + "github.com/liqotech/liqo/pkg/liqoctl/rest/gatewayserver" + "github.com/liqotech/liqo/pkg/liqoctl/rest/publickey" "github.com/liqotech/liqo/pkg/liqoctl/wait" liqogetters "github.com/liqotech/liqo/pkg/utils/getters" liqolabels "github.com/liqotech/liqo/pkg/utils/labels" @@ -54,6 +57,26 @@ func NewCluster(local, remote *factory.Factory) *Cluster { // Init initializes the cluster struct. func (c *Cluster) Init(ctx context.Context) error { + // Set cluster identity. + if err := c.SetClusterIdentity(ctx); err != nil { + return err + } + + // Get network configuration. + s := c.local.Printer.StartSpinner("Retrieving network configuration") + conf, err := configuration.ForgeLocalConfiguration(ctx, c.local.CRClient, c.local.Namespace, c.local.LiqoNamespace) + if err != nil { + s.Fail(fmt.Sprintf("An error occurred while retrieving network configuration: %v", output.PrettyErr(err))) + return err + } + c.NetworkConfiguration = conf + s.Success("Network configuration correctly retrieved") + + return nil +} + +// SetClusterIdentity set cluster identity retrieving it from the Liqo configmap. +func (c *Cluster) SetClusterIdentity(ctx context.Context) error { // Get cluster identity. s := c.local.Printer.StartSpinner("Retrieving cluster identity") selector, err := metav1.LabelSelectorAsSelector(&liqolabels.ClusterIDConfigMapLabelSelector) @@ -73,17 +96,6 @@ func (c *Cluster) Init(ctx context.Context) error { } c.clusterIdentity = clusterIdentity s.Success("Cluster identity correctly retrieved") - - // Get network configuration. - s = c.local.Printer.StartSpinner("Retrieving network configuration") - conf, err := configuration.ForgeLocalConfiguration(ctx, c.local.CRClient, c.local.Namespace, c.local.LiqoNamespace) - if err != nil { - s.Fail(fmt.Sprintf("An error occurred while retrieving network configuration: %v", output.PrettyErr(err))) - return err - } - c.NetworkConfiguration = conf - s.Success("Network configuration correctly retrieved") - return nil } @@ -113,3 +125,63 @@ func (c *Cluster) SetupConfiguration(ctx context.Context, s.Success("Network configuration correctly set up") return nil } + +// EnsureGatewayServer create or updates a GatewayServer. +func (c *Cluster) EnsureGatewayServer(ctx context.Context, name string, opts *gatewayserver.ForgeOptions) (*networkingv1alpha1.GatewayServer, error) { + s := c.local.Printer.StartSpinner("Setting up Gateway Server") + gwServer, err := gatewayserver.ForgeGatewayServer(name, c.local.Namespace, opts) + if err != nil { + s.Fail(fmt.Sprintf("An error occurred while forging gatewayserver: %v", output.PrettyErr(err))) + return nil, err + } + _, err = controllerutil.CreateOrUpdate(ctx, c.local.CRClient, gwServer, func() error { + return gatewayserver.MutateGatewayServer(gwServer, opts) + }) + if err != nil { + s.Fail(fmt.Sprintf("An error occurred while setting up gatewayserver: %v", output.PrettyErr(err))) + return nil, err + } + + s.Success("Gatewayserver correctly set up") + return gwServer, nil +} + +// EnsureGatewayClient create or updates a GatewayClient. +func (c *Cluster) EnsureGatewayClient(ctx context.Context, name string, opts *gatewayclient.ForgeOptions) (*networkingv1alpha1.GatewayClient, error) { + s := c.local.Printer.StartSpinner("Setting up Gateway Client") + gwClient, err := gatewayclient.ForgeGatewayClient(name, c.local.Namespace, opts) + if err != nil { + s.Fail(fmt.Sprintf("An error occurred while forging gatewayclient: %v", output.PrettyErr(err))) + return nil, err + } + _, err = controllerutil.CreateOrUpdate(ctx, c.local.CRClient, gwClient, func() error { + return gatewayclient.MutateGatewayClient(gwClient, opts) + }) + if err != nil { + s.Fail(fmt.Sprintf("An error occurred while setting up gatewayclient: %v", output.PrettyErr(err))) + return nil, err + } + + s.Success("Gatewayclient correctly set up") + return gwClient, nil +} + +// EnsurePublicKey create or updates a PublicKey. +func (c *Cluster) EnsurePublicKey(ctx context.Context, remoteClusterIdentity *discoveryv1alpha1.ClusterIdentity, key []byte) error { + s := c.local.Printer.StartSpinner("Creating PublicKey") + pubKey, err := publickey.ForgePublicKey(remoteClusterIdentity.ClusterName, c.local.Namespace, remoteClusterIdentity.ClusterID, key) + if err != nil { + s.Fail(fmt.Sprintf("An error occurred while forging publickey: %v", output.PrettyErr(err))) + return err + } + _, err = controllerutil.CreateOrUpdate(ctx, c.local.CRClient, pubKey, func() error { + return publickey.MutatePublicKey(pubKey, remoteClusterIdentity.ClusterID, key) + }) + if err != nil { + s.Fail(fmt.Sprintf("An error occurred while creating publickey: %v", output.PrettyErr(err))) + return err + } + + s.Success("PublicKey correctly created") + return nil +} diff --git a/pkg/liqoctl/network/handler.go b/pkg/liqoctl/network/handler.go index c9e997a5b4..ca80bb221e 100644 --- a/pkg/liqoctl/network/handler.go +++ b/pkg/liqoctl/network/handler.go @@ -18,7 +18,15 @@ import ( "context" "time" + v1 "k8s.io/api/core/v1" + "k8s.io/client-go/kubernetes" + + networkingv1alpha1 "github.com/liqotech/liqo/apis/networking/v1alpha1" "github.com/liqotech/liqo/pkg/liqoctl/factory" + "github.com/liqotech/liqo/pkg/liqoctl/rest/gatewayclient" + "github.com/liqotech/liqo/pkg/liqoctl/rest/gatewayserver" + "github.com/liqotech/liqo/pkg/liqoctl/rest/publickey" + argsutils "github.com/liqotech/liqo/pkg/utils/args" ) // Options encapsulates the arguments of the network command. @@ -28,6 +36,30 @@ type Options struct { Timeout time.Duration Wait bool + + ServerGatewayType string + ServerTemplateName string + ServerTemplateNamespace string + ServerServiceType *argsutils.StringEnum + ServerPort int32 + ServerMTU int + + ClientGatewayType string + ClientTemplateName string + ClientTemplateNamespace string + ClientMTU int + + DisableSharingKeys bool + Proxy bool +} + +// NewOptions returns a new Options struct. +func NewOptions(localFactory *factory.Factory) *Options { + return &Options{ + LocalFactory: localFactory, + ServerServiceType: argsutils.NewEnum( + []string{string(v1.ServiceTypeLoadBalancer), string(v1.ServiceTypeNodePort)}, string(gatewayserver.DefaultServiceType)), + } } // RunInit initializes the liqo networking between two clusters. @@ -71,3 +103,104 @@ func (o *Options) RunInit(ctx context.Context) error { return nil } + +// RunConnect connect two clusters using liqo networking. +func (o *Options) RunConnect(ctx context.Context) error { + ctx, cancel := context.WithTimeout(ctx, o.Timeout) + defer cancel() + + // Create and initialize cluster 1. + cluster1 := NewCluster(o.LocalFactory, o.RemoteFactory) + if err := cluster1.SetClusterIdentity(ctx); err != nil { + return err + } + + // Create and initialize cluster 2. + cluster2 := NewCluster(o.RemoteFactory, o.LocalFactory) + if err := cluster2.SetClusterIdentity(ctx); err != nil { + return err + } + + // Create gateway server on cluster 1 + gwServer, err := cluster1.EnsureGatewayServer(ctx, + cluster2.clusterIdentity.ClusterName, + o.newGatewayServerForgeOptions(o.LocalFactory.KubeClient, cluster2.clusterIdentity.ClusterID)) + if err != nil { + return err + } + + // Wait for the endpoint status of the gateway server to be set + if err := cluster1.Waiter.ForGatewayServerStatusEndpoint(ctx, gwServer); err != nil { + return err + } + + // Create gateway client on cluster 2 + gwClient, err := cluster2.EnsureGatewayClient(ctx, + cluster1.clusterIdentity.ClusterName, + o.newGatewayClientForgeOptions(o.RemoteFactory.KubeClient, cluster1.clusterIdentity.ClusterID, gwServer.Status.Endpoint)) + if err != nil { + return err + } + + // If sharing keys is disabled, return immediately + if o.DisableSharingKeys { + return nil + } + + // Wait for gateway server to set secret reference (containing the server public key) in the status + err = cluster1.Waiter.ForGatewayServerSecretRef(ctx, gwServer) + if err != nil { + return err + } + keyServer, err := publickey.ExtractKeyFromSecretRef(ctx, cluster1.local.CRClient, gwServer.Status.SecretRef) + if err != nil { + return err + } + + // Create PublicKey of gateway server on cluster 2 + if err := cluster2.EnsurePublicKey(ctx, cluster1.clusterIdentity, keyServer); err != nil { + return err + } + + // Wait for gateway client to set secret reference (containing the client public key) in the status + err = cluster2.Waiter.ForGatewayClientSecretRef(ctx, gwClient) + if err != nil { + return err + } + keyClient, err := publickey.ExtractKeyFromSecretRef(ctx, cluster2.local.CRClient, gwClient.Status.SecretRef) + if err != nil { + return err + } + + // Create PublicKey of gateway client on cluster 1 + return cluster1.EnsurePublicKey(ctx, cluster2.clusterIdentity, keyClient) +} + +func (o *Options) newGatewayServerForgeOptions(kubeClient kubernetes.Interface, remoteClusterID string) *gatewayserver.ForgeOptions { + return &gatewayserver.ForgeOptions{ + KubeClient: kubeClient, + RemoteClusterID: remoteClusterID, + GatewayType: o.ServerGatewayType, + TemplateName: o.ServerTemplateName, + TemplateNamespace: o.ServerTemplateNamespace, + ServiceType: v1.ServiceType(o.ServerServiceType.Value), + MTU: o.ServerMTU, + Port: o.ServerPort, + Proxy: o.Proxy, + } +} + +func (o *Options) newGatewayClientForgeOptions(kubeClient kubernetes.Interface, remoteClusterID string, + serverEndpoint *networkingv1alpha1.EndpointStatus) *gatewayclient.ForgeOptions { + return &gatewayclient.ForgeOptions{ + KubeClient: kubeClient, + RemoteClusterID: remoteClusterID, + GatewayType: o.ClientGatewayType, + TemplateName: o.ClientTemplateName, + TemplateNamespace: o.ClientTemplateNamespace, + MTU: o.ClientMTU, + Addresses: serverEndpoint.Addresses, + Port: serverEndpoint.Port, + Protocol: string(*serverEndpoint.Protocol), + } +} diff --git a/pkg/liqoctl/rest/gatewayclient/create.go b/pkg/liqoctl/rest/gatewayclient/create.go new file mode 100644 index 0000000000..af9baab76c --- /dev/null +++ b/pkg/liqoctl/rest/gatewayclient/create.go @@ -0,0 +1,163 @@ +// Copyright 2019-2023 The Liqo 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. + +package gatewayclient + +import ( + "context" + "fmt" + "os" + "time" + + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/cli-runtime/pkg/printers" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + networkingv1alpha1 "github.com/liqotech/liqo/apis/networking/v1alpha1" + "github.com/liqotech/liqo/pkg/liqoctl/completion" + "github.com/liqotech/liqo/pkg/liqoctl/output" + "github.com/liqotech/liqo/pkg/liqoctl/rest" + "github.com/liqotech/liqo/pkg/utils/args" +) + +const liqoctlCreateGatewayClientLongHelp = `Create a Gateway Client. + +The GatewayClient resource is used to define a Gateway Client for the external network. + +Examples: + $ {{ .Executable }} create gatewayclient my-gw-client \ + --cluster-id my-cluster-id \ + --type networking.liqo.io/v1alpha1/wggatewayclients` + +// Create creates a GatewayClient. +func (o *Options) Create(ctx context.Context, options *rest.CreateOptions) *cobra.Command { + outputFormat := args.NewEnum([]string{"json", "yaml"}, "") + + o.createOptions = options + + cmd := &cobra.Command{ + Use: "gatewayclient", + Aliases: []string{"gatewayclients", "client", "clients", "gwc"}, + Short: "Create a Gateway Client", + Long: liqoctlCreateGatewayClientLongHelp, + Args: cobra.ExactArgs(1), + + PreRun: func(cmd *cobra.Command, args []string) { + options.OutputFormat = outputFormat.Value + options.Name = args[0] + o.createOptions = options + }, + + Run: func(cmd *cobra.Command, args []string) { + output.ExitOnErr(o.handleCreate(ctx)) + }, + } + + cmd.Flags().VarP(outputFormat, "output", "o", + "Output the resulting GatewayClient resource, instead of applying it. Supported formats: json, yaml") + + cmd.Flags().StringVar(&o.ClusterID, "cluster-id", "", "The cluster ID of the remote cluster") + cmd.Flags().StringVar(&o.GatewayType, "type", DefaultGatewayType, "Type of Gateway Client. Default: wireguard") + cmd.Flags().StringVar(&o.TemplateName, "template-name", DefaultTemplateName, "Name of the Gateway Client template") + cmd.Flags().StringVar(&o.TemplateNamespace, "template-namespace", DefaultTemplateNamespace, "Namespace of the Gateway Client template") + cmd.Flags().IntVar(&o.MTU, "mtu", DefaultMTU, "MTU of Gateway Client") + cmd.Flags().StringSliceVar(&o.Addresses, "addresses", []string{}, "Addresses of Gateway Server") + cmd.Flags().Int32Var(&o.Port, "port", 0, "Port of Gateway Server") + cmd.Flags().StringVar(&o.Protocol, "protocol", DefaultProtocol, "Gateway Protocol") + cmd.Flags().BoolVar(&o.Wait, "wait", DefaultWait, "Wait for the Gateway Client to be ready") + + runtime.Must(cmd.MarkFlagRequired("cluster-id")) + runtime.Must(cmd.MarkFlagRequired("addresses")) + runtime.Must(cmd.MarkFlagRequired("port")) + + runtime.Must(cmd.RegisterFlagCompletionFunc("output", completion.Enumeration(outputFormat.Allowed))) + runtime.Must(cmd.RegisterFlagCompletionFunc("cluster-id", completion.ClusterIDs(ctx, + o.createOptions.Factory, completion.NoLimit))) + + return cmd +} + +func (o *Options) handleCreate(ctx context.Context) error { + opts := o.createOptions + + gwClient, err := ForgeGatewayClient(opts.Name, opts.Namespace, o.getForgeOptions()) + if err != nil { + opts.Printer.CheckErr(err) + return err + } + + if opts.OutputFormat != "" { + opts.Printer.CheckErr(o.output(gwClient)) + return nil + } + + s := opts.Printer.StartSpinner("Creating gatewayclient") + + _, err = controllerutil.CreateOrUpdate(ctx, opts.CRClient, gwClient, func() error { + return MutateGatewayClient(gwClient, o.getForgeOptions()) + }) + if err != nil { + s.Fail("Unable to create gatewayclient: %v", output.PrettyErr(err)) + return err + } + s.Success("Gatewayclient created") + + if o.Wait { + s = opts.Printer.StartSpinner("Waiting for gatewayclient to be ready") + interval := 1 * time.Second + if err := wait.PollUntilContextCancel(ctx, interval, false, func(context.Context) (done bool, err error) { + var appliedGwClient networkingv1alpha1.GatewayClient + err = opts.CRClient.Get(ctx, types.NamespacedName{ + Namespace: gwClient.Namespace, + Name: gwClient.Name, + }, &appliedGwClient) + if err != nil { + return false, err + } + + return appliedGwClient.Status.ClientRef != nil, nil + }); err != nil { + s.Fail("Unable to wait for gatewayclient to be ready: %v", output.PrettyErr(err)) + return err + } + s.Success("gatewayclient is ready") + } + + return nil +} + +// output implements the logic to output the generated Gateway Client resource. +func (o *Options) output(gwClient *networkingv1alpha1.GatewayClient) error { + var outputFormat string + switch { + case o.createOptions != nil: + outputFormat = o.createOptions.OutputFormat + default: + return fmt.Errorf("unable to determine output format") + } + var printer printers.ResourcePrinter + switch outputFormat { + case "yaml": + printer = &printers.YAMLPrinter{} + case "json": + printer = &printers.JSONPrinter{} + default: + return fmt.Errorf("unsupported output format %q", outputFormat) + } + + return printer.PrintObj(gwClient, os.Stdout) +} diff --git a/pkg/liqoctl/rest/gatewayclient/delete.go b/pkg/liqoctl/rest/gatewayclient/delete.go new file mode 100644 index 0000000000..e465f54644 --- /dev/null +++ b/pkg/liqoctl/rest/gatewayclient/delete.go @@ -0,0 +1,28 @@ +// Copyright 2019-2023 The Liqo 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. + +package gatewayclient + +import ( + "context" + + "github.com/spf13/cobra" + + "github.com/liqotech/liqo/pkg/liqoctl/rest" +) + +// Delete deletes a GatewayClient. +func (o *Options) Delete(_ context.Context, _ *rest.DeleteOptions) *cobra.Command { + panic("not implemented") +} diff --git a/pkg/liqoctl/rest/gatewayclient/doc.go b/pkg/liqoctl/rest/gatewayclient/doc.go new file mode 100644 index 0000000000..d07389c452 --- /dev/null +++ b/pkg/liqoctl/rest/gatewayclient/doc.go @@ -0,0 +1,16 @@ +// Copyright 2019-2023 The Liqo 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. + +// Package gatewayclient contains the logic to manage GatewayClients. +package gatewayclient diff --git a/pkg/liqoctl/rest/gatewayclient/generate.go b/pkg/liqoctl/rest/gatewayclient/generate.go new file mode 100644 index 0000000000..66b3f743ad --- /dev/null +++ b/pkg/liqoctl/rest/gatewayclient/generate.go @@ -0,0 +1,28 @@ +// Copyright 2019-2023 The Liqo 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. + +package gatewayclient + +import ( + "context" + + "github.com/spf13/cobra" + + "github.com/liqotech/liqo/pkg/liqoctl/rest" +) + +// Generate generates a GatewayClient. +func (o *Options) Generate(_ context.Context, _ *rest.GenerateOptions) *cobra.Command { + panic("not implemented") +} diff --git a/pkg/liqoctl/rest/gatewayclient/get.go b/pkg/liqoctl/rest/gatewayclient/get.go new file mode 100644 index 0000000000..def9f2f776 --- /dev/null +++ b/pkg/liqoctl/rest/gatewayclient/get.go @@ -0,0 +1,28 @@ +// Copyright 2019-2023 The Liqo 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. + +package gatewayclient + +import ( + "context" + + "github.com/spf13/cobra" + + "github.com/liqotech/liqo/pkg/liqoctl/rest" +) + +// Get implements the get command. +func (o *Options) Get(_ context.Context, _ *rest.GetOptions) *cobra.Command { + panic("not implemented") +} diff --git a/pkg/liqoctl/rest/gatewayclient/types.go b/pkg/liqoctl/rest/gatewayclient/types.go new file mode 100644 index 0000000000..dfe7a12be3 --- /dev/null +++ b/pkg/liqoctl/rest/gatewayclient/types.go @@ -0,0 +1,87 @@ +// Copyright 2019-2023 The Liqo 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. + +package gatewayclient + +import ( + "k8s.io/client-go/kubernetes" + + rest "github.com/liqotech/liqo/pkg/liqoctl/rest" +) + +// Default values for the gatewayclient command. +const ( + DefaultGatewayType = "networking.liqo.io/v1alpha1/wggatewayclienttemplates" + DefaultTemplateName = "wireguard-client" + DefaultTemplateNamespace = "liqo" + DefaultMTU = 1450 + DefaultProtocol = "UDP" + DefaultWait = false +) + +// Options encapsulates the arguments of the gatewayclient command. +type Options struct { + createOptions *rest.CreateOptions + + ClusterID string + GatewayType string + TemplateName string + TemplateNamespace string + MTU int + Addresses []string + Port int32 + Protocol string + Wait bool +} + +var _ rest.API = &Options{} + +// GatewayClient returns the rest API for the gatewayclient command. +func GatewayClient() rest.API { + return &Options{} +} + +// APIOptions returns the APIOptions for the gatewayclient API. +func (o *Options) APIOptions() *rest.APIOptions { + return &rest.APIOptions{ + EnableCreate: true, + } +} + +// ForgeOptions encapsulate the options to forge a gatewayclient. +type ForgeOptions struct { + KubeClient kubernetes.Interface + RemoteClusterID string + GatewayType string + TemplateName string + TemplateNamespace string + MTU int + Addresses []string + Port int32 + Protocol string +} + +func (o *Options) getForgeOptions() *ForgeOptions { + return &ForgeOptions{ + KubeClient: o.createOptions.KubeClient, + RemoteClusterID: o.ClusterID, + GatewayType: o.GatewayType, + TemplateName: o.TemplateName, + TemplateNamespace: o.TemplateNamespace, + MTU: o.MTU, + Addresses: o.Addresses, + Port: o.Port, + Protocol: o.Protocol, + } +} diff --git a/pkg/liqoctl/rest/gatewayclient/update.go b/pkg/liqoctl/rest/gatewayclient/update.go new file mode 100644 index 0000000000..4b5f851daa --- /dev/null +++ b/pkg/liqoctl/rest/gatewayclient/update.go @@ -0,0 +1,28 @@ +// Copyright 2019-2023 The Liqo 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. + +package gatewayclient + +import ( + "context" + + "github.com/spf13/cobra" + + "github.com/liqotech/liqo/pkg/liqoctl/rest" +) + +// Update implements the update command. +func (o *Options) Update(_ context.Context, _ *rest.UpdateOptions) *cobra.Command { + panic("not implemented") +} diff --git a/pkg/liqoctl/rest/gatewayclient/utils.go b/pkg/liqoctl/rest/gatewayclient/utils.go new file mode 100644 index 0000000000..cc5bef6119 --- /dev/null +++ b/pkg/liqoctl/rest/gatewayclient/utils.go @@ -0,0 +1,83 @@ +// Copyright 2019-2023 The Liqo 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. + +package gatewayclient + +import ( + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + networkingv1alpha1 "github.com/liqotech/liqo/apis/networking/v1alpha1" + liqoconsts "github.com/liqotech/liqo/pkg/consts" + enutils "github.com/liqotech/liqo/pkg/liqo-controller-manager/external-network/utils" +) + +// ForgeGatewayClient forges a GatewayClient. +func ForgeGatewayClient(name, namespace string, o *ForgeOptions) (*networkingv1alpha1.GatewayClient, error) { + gwClient := &networkingv1alpha1.GatewayClient{ + TypeMeta: metav1.TypeMeta{ + Kind: networkingv1alpha1.GatewayClientKind, + APIVersion: networkingv1alpha1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Labels: map[string]string{ + liqoconsts.RemoteClusterID: o.RemoteClusterID, + }, + }, + } + err := MutateGatewayClient(gwClient, o) + if err != nil { + return nil, err + } + return gwClient, nil +} + +// MutateGatewayClient mutates a GatewayClient. +func MutateGatewayClient(gwClient *networkingv1alpha1.GatewayClient, o *ForgeOptions) error { + gwClient.Kind = networkingv1alpha1.GatewayClientKind + gwClient.APIVersion = networkingv1alpha1.GroupVersion.String() + + if gwClient.Labels == nil { + gwClient.Labels = make(map[string]string) + } + gwClient.Labels[liqoconsts.RemoteClusterID] = o.RemoteClusterID + + gwClient.Spec.MTU = o.MTU + + protocol := v1.Protocol(o.Protocol) + gwClient.Spec.Endpoint = networkingv1alpha1.EndpointStatus{ + Addresses: o.Addresses, + Port: o.Port, + Protocol: &protocol, + } + + gvr, err := enutils.ParseGroupVersionResource(o.GatewayType) + if err != nil { + return err + } + kind, err := enutils.ResourceToKind(gvr, o.KubeClient) + if err != nil { + return err + } + gwClient.Spec.ClientTemplateRef = v1.ObjectReference{ + Name: o.TemplateName, + Namespace: o.TemplateNamespace, + Kind: kind, + APIVersion: gvr.GroupVersion().String(), + } + + return nil +} diff --git a/pkg/liqoctl/rest/gatewayserver/create.go b/pkg/liqoctl/rest/gatewayserver/create.go new file mode 100644 index 0000000000..65bef8cfd8 --- /dev/null +++ b/pkg/liqoctl/rest/gatewayserver/create.go @@ -0,0 +1,163 @@ +// Copyright 2019-2023 The Liqo 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. + +package gatewayserver + +import ( + "context" + "fmt" + "os" + "time" + + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/cli-runtime/pkg/printers" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + networkingv1alpha1 "github.com/liqotech/liqo/apis/networking/v1alpha1" + "github.com/liqotech/liqo/pkg/liqoctl/completion" + "github.com/liqotech/liqo/pkg/liqoctl/output" + "github.com/liqotech/liqo/pkg/liqoctl/rest" + "github.com/liqotech/liqo/pkg/utils/args" +) + +const liqoctlCreateGatewayServerLongHelp = `Create a Gateway Server. + +The GatewayServer resource is used to define a Gateway Server for the external network. + +Examples: + $ {{ .Executable }} create gatewayserver my-gw-server \ + --cluster-id my-cluster-id \ + --type networking.liqo.io/v1alpha1/wggatewayservers --service-type LoadBalancer` + +// Create creates a GatewayServer. +func (o *Options) Create(ctx context.Context, options *rest.CreateOptions) *cobra.Command { + outputFormat := args.NewEnum([]string{"json", "yaml"}, "") + + o.createOptions = options + + cmd := &cobra.Command{ + Use: "gatewayserver", + Aliases: []string{"gatewayservers", "server", "servers", "gws"}, + Short: "Create a Gateway Server", + Long: liqoctlCreateGatewayServerLongHelp, + Args: cobra.ExactArgs(1), + + PreRun: func(cmd *cobra.Command, args []string) { + options.OutputFormat = outputFormat.Value + options.Name = args[0] + o.createOptions = options + }, + + Run: func(cmd *cobra.Command, args []string) { + output.ExitOnErr(o.handleCreate(ctx)) + }, + } + + cmd.Flags().VarP(outputFormat, "output", "o", + "Output the resulting GatewayServer resource, instead of applying it. Supported formats: json, yaml") + + cmd.Flags().StringVar(&o.ClusterID, "cluster-id", "", "The cluster ID of the remote cluster") + cmd.Flags().StringVar(&o.GatewayType, "type", DefaultGatewayType, + "Type of Gateway Server. Leave empty to use default Liqo implementation of WireGuard") + cmd.Flags().StringVar(&o.TemplateName, "template-name", DefaultTemplateName, "Name of the Gateway Server template") + cmd.Flags().StringVar(&o.TemplateNamespace, "template-namespace", DefaultTemplateNamespace, "Namespace of the Gateway Server template") + cmd.Flags().Var(o.ServiceType, "service-type", fmt.Sprintf("Service type of Gateway Server. Default: %s", DefaultServiceType)) + cmd.Flags().IntVar(&o.MTU, "mtu", DefaultMTU, "MTU of Gateway Server") + cmd.Flags().Int32Var(&o.Port, "port", DefaultPort, "Port of Gateway Server") + cmd.Flags().BoolVar(&o.Proxy, "proxy", DefaultProxy, "Enable proxy for the Gateway Server") + cmd.Flags().BoolVar(&o.Wait, "wait", DefaultWait, "Wait for the Gateway Server to be ready") + + runtime.Must(cmd.MarkFlagRequired("cluster-id")) + + runtime.Must(cmd.RegisterFlagCompletionFunc("output", completion.Enumeration(outputFormat.Allowed))) + runtime.Must(cmd.RegisterFlagCompletionFunc("cluster-id", completion.ClusterIDs(ctx, + o.createOptions.Factory, completion.NoLimit))) + runtime.Must(cmd.RegisterFlagCompletionFunc("service-type", completion.Enumeration(o.ServiceType.Allowed))) + + return cmd +} + +func (o *Options) handleCreate(ctx context.Context) error { + opts := o.createOptions + + gwServer, err := ForgeGatewayServer(opts.Name, opts.Namespace, o.getForgeOptions()) + if err != nil { + opts.Printer.CheckErr(err) + return err + } + + if opts.OutputFormat != "" { + opts.Printer.CheckErr(o.output(gwServer)) + return nil + } + + s := opts.Printer.StartSpinner("Creating gatewayserver") + + _, err = controllerutil.CreateOrUpdate(ctx, opts.CRClient, gwServer, func() error { + return MutateGatewayServer(gwServer, o.getForgeOptions()) + }) + if err != nil { + s.Fail("Unable to create gatewayserver: %v", output.PrettyErr(err)) + return err + } + s.Success("Gatewayserver created") + + if o.Wait { + s = opts.Printer.StartSpinner("Waiting for gatewayserver to be ready") + interval := 1 * time.Second + if err := wait.PollUntilContextCancel(ctx, interval, false, func(context.Context) (done bool, err error) { + var appliedGwServer networkingv1alpha1.GatewayServer + err = opts.CRClient.Get(ctx, types.NamespacedName{ + Namespace: gwServer.Namespace, + Name: gwServer.Name, + }, &appliedGwServer) + if err != nil { + return false, err + } + + return appliedGwServer.Status.Endpoint != nil && appliedGwServer.Status.ServerRef != nil, nil + }); err != nil { + s.Fail("Unable to wait for gatewayserver to be ready: %v", output.PrettyErr(err)) + return err + } + s.Success("gatewayserver is ready") + } + + return nil +} + +// output implements the logic to output the generated Gateway Server resource. +func (o *Options) output(gwServer *networkingv1alpha1.GatewayServer) error { + var outputFormat string + switch { + case o.createOptions != nil: + outputFormat = o.createOptions.OutputFormat + default: + return fmt.Errorf("unable to determine output format") + } + var printer printers.ResourcePrinter + switch outputFormat { + case "yaml": + printer = &printers.YAMLPrinter{} + case "json": + printer = &printers.JSONPrinter{} + default: + return fmt.Errorf("unsupported output format %q", outputFormat) + } + + return printer.PrintObj(gwServer, os.Stdout) +} diff --git a/pkg/liqoctl/rest/gatewayserver/delete.go b/pkg/liqoctl/rest/gatewayserver/delete.go new file mode 100644 index 0000000000..0655f5db56 --- /dev/null +++ b/pkg/liqoctl/rest/gatewayserver/delete.go @@ -0,0 +1,28 @@ +// Copyright 2019-2023 The Liqo 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. + +package gatewayserver + +import ( + "context" + + "github.com/spf13/cobra" + + "github.com/liqotech/liqo/pkg/liqoctl/rest" +) + +// Delete deletes a GatewayServer. +func (o *Options) Delete(_ context.Context, _ *rest.DeleteOptions) *cobra.Command { + panic("not implemented") +} diff --git a/pkg/liqoctl/rest/gatewayserver/doc.go b/pkg/liqoctl/rest/gatewayserver/doc.go new file mode 100644 index 0000000000..805bccd343 --- /dev/null +++ b/pkg/liqoctl/rest/gatewayserver/doc.go @@ -0,0 +1,16 @@ +// Copyright 2019-2023 The Liqo 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. + +// Package gatewayserver contains the logic to manage GatewayServers. +package gatewayserver diff --git a/pkg/liqoctl/rest/gatewayserver/generate.go b/pkg/liqoctl/rest/gatewayserver/generate.go new file mode 100644 index 0000000000..69cd2143f3 --- /dev/null +++ b/pkg/liqoctl/rest/gatewayserver/generate.go @@ -0,0 +1,28 @@ +// Copyright 2019-2023 The Liqo 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. + +package gatewayserver + +import ( + "context" + + "github.com/spf13/cobra" + + "github.com/liqotech/liqo/pkg/liqoctl/rest" +) + +// Generate generates a GatewayServer. +func (o *Options) Generate(_ context.Context, _ *rest.GenerateOptions) *cobra.Command { + panic("not implemented") +} diff --git a/pkg/liqoctl/rest/gatewayserver/get.go b/pkg/liqoctl/rest/gatewayserver/get.go new file mode 100644 index 0000000000..ea8fce1724 --- /dev/null +++ b/pkg/liqoctl/rest/gatewayserver/get.go @@ -0,0 +1,28 @@ +// Copyright 2019-2023 The Liqo 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. + +package gatewayserver + +import ( + "context" + + "github.com/spf13/cobra" + + "github.com/liqotech/liqo/pkg/liqoctl/rest" +) + +// Get implements the get command. +func (o *Options) Get(_ context.Context, _ *rest.GetOptions) *cobra.Command { + panic("not implemented") +} diff --git a/pkg/liqoctl/rest/gatewayserver/types.go b/pkg/liqoctl/rest/gatewayserver/types.go new file mode 100644 index 0000000000..c623312218 --- /dev/null +++ b/pkg/liqoctl/rest/gatewayserver/types.go @@ -0,0 +1,94 @@ +// Copyright 2019-2023 The Liqo 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. + +package gatewayserver + +import ( + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/kubernetes" + + liqorest "github.com/liqotech/liqo/pkg/liqoctl/rest" + argsutils "github.com/liqotech/liqo/pkg/utils/args" +) + +// Default values for the gatewayserver command. +const ( + DefaultGatewayType = "networking.liqo.io/v1alpha1/wggatewayservertemplates" + DefaultTemplateName = "wireguard-server" + DefaultTemplateNamespace = "liqo" + DefaultServiceType = corev1.ServiceTypeLoadBalancer + DefaultMTU = 1450 + DefaultPort = 51820 + DefaultProxy = false + DefaultWait = false +) + +// Options encapsulates the arguments of the gatewayserver command. +type Options struct { + createOptions *liqorest.CreateOptions + + ClusterID string + GatewayType string + TemplateName string + TemplateNamespace string + ServiceType *argsutils.StringEnum + MTU int + Port int32 + Proxy bool + Wait bool +} + +var _ liqorest.API = &Options{} + +// GatewayServer returns the rest API for the gatewayserver command. +func GatewayServer() liqorest.API { + return &Options{ + ServiceType: argsutils.NewEnum( + []string{string(corev1.ServiceTypeLoadBalancer), string(corev1.ServiceTypeNodePort)}, string(DefaultServiceType)), + } +} + +// APIOptions returns the APIOptions for the gatewayserver API. +func (o *Options) APIOptions() *liqorest.APIOptions { + return &liqorest.APIOptions{ + EnableCreate: true, + } +} + +// ForgeOptions encapsulate the options to forge a gatewayserver. +type ForgeOptions struct { + KubeClient kubernetes.Interface + RemoteClusterID string + GatewayType string + TemplateName string + TemplateNamespace string + ServiceType corev1.ServiceType + MTU int + Port int32 + Proxy bool +} + +func (o *Options) getForgeOptions() *ForgeOptions { + return &ForgeOptions{ + KubeClient: o.createOptions.KubeClient, + RemoteClusterID: o.ClusterID, + GatewayType: o.GatewayType, + TemplateName: o.TemplateName, + TemplateNamespace: o.TemplateNamespace, + ServiceType: corev1.ServiceType(o.ServiceType.Value), + MTU: o.MTU, + Port: o.Port, + Proxy: o.Proxy, + } +} diff --git a/pkg/liqoctl/rest/gatewayserver/update.go b/pkg/liqoctl/rest/gatewayserver/update.go new file mode 100644 index 0000000000..e0e9044b86 --- /dev/null +++ b/pkg/liqoctl/rest/gatewayserver/update.go @@ -0,0 +1,28 @@ +// Copyright 2019-2023 The Liqo 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. + +package gatewayserver + +import ( + "context" + + "github.com/spf13/cobra" + + "github.com/liqotech/liqo/pkg/liqoctl/rest" +) + +// Update implements the update command. +func (o *Options) Update(_ context.Context, _ *rest.UpdateOptions) *cobra.Command { + panic("not implemented") +} diff --git a/pkg/liqoctl/rest/gatewayserver/utils.go b/pkg/liqoctl/rest/gatewayserver/utils.go new file mode 100644 index 0000000000..1434b135e4 --- /dev/null +++ b/pkg/liqoctl/rest/gatewayserver/utils.go @@ -0,0 +1,80 @@ +// Copyright 2019-2023 The Liqo 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. + +package gatewayserver + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + networkingv1alpha1 "github.com/liqotech/liqo/apis/networking/v1alpha1" + liqoconsts "github.com/liqotech/liqo/pkg/consts" + enutils "github.com/liqotech/liqo/pkg/liqo-controller-manager/external-network/utils" +) + +// ForgeGatewayServer forges a GatewayServer. +func ForgeGatewayServer(name, namespace string, o *ForgeOptions) (*networkingv1alpha1.GatewayServer, error) { + gwServer := &networkingv1alpha1.GatewayServer{ + TypeMeta: metav1.TypeMeta{ + Kind: networkingv1alpha1.GatewayServerKind, + APIVersion: networkingv1alpha1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Labels: map[string]string{ + liqoconsts.RemoteClusterID: o.RemoteClusterID, + }, + }, + } + err := MutateGatewayServer(gwServer, o) + if err != nil { + return nil, err + } + return gwServer, nil +} + +// MutateGatewayServer mutates a GatewayServer. +func MutateGatewayServer(gwServer *networkingv1alpha1.GatewayServer, o *ForgeOptions) error { + gwServer.Kind = networkingv1alpha1.GatewayServerKind + gwServer.APIVersion = networkingv1alpha1.GroupVersion.String() + + if gwServer.Labels == nil { + gwServer.Labels = make(map[string]string) + } + gwServer.Labels[liqoconsts.RemoteClusterID] = o.RemoteClusterID + + gwServer.Spec.MTU = o.MTU + gwServer.Spec.Endpoint = networkingv1alpha1.Endpoint{ + Port: o.Port, + ServiceType: o.ServiceType, + } + + gvr, err := enutils.ParseGroupVersionResource(o.GatewayType) + if err != nil { + return err + } + kind, err := enutils.ResourceToKind(gvr, o.KubeClient) + if err != nil { + return err + } + gwServer.Spec.ServerTemplateRef = corev1.ObjectReference{ + Name: o.TemplateName, + Namespace: o.TemplateNamespace, + Kind: kind, + APIVersion: gvr.GroupVersion().String(), + } + + return nil +} diff --git a/pkg/liqoctl/rest/publickey/create.go b/pkg/liqoctl/rest/publickey/create.go new file mode 100644 index 0000000000..d8d772b436 --- /dev/null +++ b/pkg/liqoctl/rest/publickey/create.go @@ -0,0 +1,131 @@ +// Copyright 2019-2023 The Liqo 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. + +package publickey + +import ( + "context" + "fmt" + "os" + + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/cli-runtime/pkg/printers" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + networkingv1alpha1 "github.com/liqotech/liqo/apis/networking/v1alpha1" + "github.com/liqotech/liqo/pkg/liqoctl/completion" + "github.com/liqotech/liqo/pkg/liqoctl/output" + "github.com/liqotech/liqo/pkg/liqoctl/rest" + "github.com/liqotech/liqo/pkg/utils/args" +) + +const liqoctlCreatePublicKeyLongHelp = `Create a PublicKey. + +The PublicKey resource is used to define a PublicKey for the external network. + +Examples: + $ {{ .Executable }} create publickey my-public-key --cluster-id my-cluster-id --type server --gateway-name my-gateway` + +// Create creates a PublicKey. +func (o *Options) Create(ctx context.Context, options *rest.CreateOptions) *cobra.Command { + outputFormat := args.NewEnum([]string{"json", "yaml"}, "") + + o.createOptions = options + + cmd := &cobra.Command{ + Use: "publickey", + Aliases: []string{"publickeys", "publickeies"}, + Short: "Create a Public Key", + Long: liqoctlCreatePublicKeyLongHelp, + Args: cobra.ExactArgs(1), + + PreRun: func(cmd *cobra.Command, args []string) { + options.OutputFormat = outputFormat.Value + options.Name = args[0] + o.createOptions = options + }, + + Run: func(cmd *cobra.Command, args []string) { + output.ExitOnErr(o.handleCreate(ctx)) + }, + } + + cmd.Flags().VarP(outputFormat, "output", "o", + "Output the resulting PublicKey resource, instead of applying it. Supported formats: json, yaml") + + cmd.Flags().StringVar(&o.ClusterID, "cluster-id", "", "The cluster ID of the remote cluster") + cmd.Flags().BytesBase64Var(&o.PublicKey, "public-key", nil, "The public key to be used for the Gateway") + + runtime.Must(cmd.MarkFlagRequired("cluster-id")) + runtime.Must(cmd.MarkFlagRequired("public-key")) + + runtime.Must(cmd.RegisterFlagCompletionFunc("output", completion.Enumeration(outputFormat.Allowed))) + runtime.Must(cmd.RegisterFlagCompletionFunc("cluster-id", completion.ClusterIDs(ctx, + o.createOptions.Factory, completion.NoLimit))) + + return cmd +} + +func (o *Options) handleCreate(ctx context.Context) error { + opts := o.createOptions + + pubKey, err := ForgePublicKey(opts.Name, opts.Namespace, o.ClusterID, o.PublicKey) + if err != nil { + opts.Printer.CheckErr(err) + return err + } + + if opts.OutputFormat != "" { + opts.Printer.CheckErr(o.output(pubKey)) + return nil + } + + s := opts.Printer.StartSpinner("Creating publickey") + + _, err = controllerutil.CreateOrUpdate(ctx, opts.CRClient, pubKey, func() error { + return MutatePublicKey(pubKey, o.ClusterID, o.PublicKey) + }) + if err != nil { + s.Fail("Unable to create publickey: %v", output.PrettyErr(err)) + return err + } + s.Success("Publickey created") + + return nil +} + +// output implements the logic to output the generated PublicKey resource. +func (o *Options) output(pubKey *networkingv1alpha1.PublicKey) error { + var outputFormat string + switch { + case o.createOptions != nil: + outputFormat = o.createOptions.OutputFormat + case o.generateOptions != nil: + outputFormat = o.generateOptions.OutputFormat + default: + return fmt.Errorf("unable to determine output format") + } + var printer printers.ResourcePrinter + switch outputFormat { + case "yaml": + printer = &printers.YAMLPrinter{} + case "json": + printer = &printers.JSONPrinter{} + default: + return fmt.Errorf("unsupported output format %q", outputFormat) + } + + return printer.PrintObj(pubKey, os.Stdout) +} diff --git a/pkg/liqoctl/rest/publickey/delete.go b/pkg/liqoctl/rest/publickey/delete.go new file mode 100644 index 0000000000..67cf8ab5e2 --- /dev/null +++ b/pkg/liqoctl/rest/publickey/delete.go @@ -0,0 +1,28 @@ +// Copyright 2019-2023 The Liqo 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. + +package publickey + +import ( + "context" + + "github.com/spf13/cobra" + + "github.com/liqotech/liqo/pkg/liqoctl/rest" +) + +// Delete deletes a PublicKey. +func (o *Options) Delete(_ context.Context, _ *rest.DeleteOptions) *cobra.Command { + panic("not implemented") +} diff --git a/pkg/liqoctl/rest/publickey/doc.go b/pkg/liqoctl/rest/publickey/doc.go new file mode 100644 index 0000000000..f9542a796d --- /dev/null +++ b/pkg/liqoctl/rest/publickey/doc.go @@ -0,0 +1,16 @@ +// Copyright 2019-2023 The Liqo 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. + +// Package publickey contains the logic to manage PublicKeys. +package publickey diff --git a/pkg/liqoctl/rest/publickey/generate.go b/pkg/liqoctl/rest/publickey/generate.go new file mode 100644 index 0000000000..b2a7ae6337 --- /dev/null +++ b/pkg/liqoctl/rest/publickey/generate.go @@ -0,0 +1,82 @@ +// Copyright 2019-2023 The Liqo 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. + +package publickey + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/util/runtime" + + "github.com/liqotech/liqo/pkg/liqoctl/completion" + "github.com/liqotech/liqo/pkg/liqoctl/output" + "github.com/liqotech/liqo/pkg/liqoctl/rest" + "github.com/liqotech/liqo/pkg/utils/args" +) + +const liqoctlGeneratePuclicKeyHelp = `Generate the PublicKey of a Gateway Server or Client to be applied to other clusters.` + +// Generate generates a PublicKey. +func (o *Options) Generate(ctx context.Context, options *rest.GenerateOptions) *cobra.Command { + outputFormat := args.NewEnum([]string{"json", "yaml"}, "yaml") + + o.generateOptions = options + + cmd := &cobra.Command{ + Use: "publickey", + Aliases: []string{"publickeys", "publickeies"}, + Short: "Generate a Public Key", + Long: liqoctlGeneratePuclicKeyHelp, + Args: cobra.NoArgs, + + PreRun: func(cmd *cobra.Command, args []string) { + options.OutputFormat = outputFormat.Value + o.generateOptions = options + }, + + Run: func(cmd *cobra.Command, args []string) { + output.ExitOnErr(o.handleGenerate(ctx)) + }, + } + + cmd.Flags().VarP(outputFormat, "output", "o", + "Output format of the resulting PublicKey resource. Supported formats: json, yaml") + + cmd.Flags().Var(o.GatewayType, "gateway-type", fmt.Sprintf("The type of gateway resource. Allowed values: %s", o.GatewayType.Allowed)) + cmd.Flags().StringVar(&o.GatewayName, "gateway-name", "", "The name of the gateway (server or client) to pull the PublicKey from") + + runtime.Must(cmd.MarkFlagRequired("gateway-type")) + runtime.Must(cmd.MarkFlagRequired("gateway-name")) + + runtime.Must(cmd.RegisterFlagCompletionFunc("output", completion.Enumeration(outputFormat.Allowed))) + runtime.Must(cmd.RegisterFlagCompletionFunc("gateway-type", completion.Enumeration(o.GatewayType.Allowed))) + runtime.Must(cmd.RegisterFlagCompletionFunc("gateway-name", completion.Gateway(ctx, o.generateOptions.Factory, completion.NoLimit))) + + return cmd +} + +func (o *Options) handleGenerate(ctx context.Context) error { + opts := o.generateOptions + + pubKey, err := ForgePublicKeyForRemoteCluster(ctx, opts.CRClient, opts.LiqoNamespace, opts.Namespace, o.GatewayName, o.GatewayType.Value) + if err != nil { + opts.Printer.CheckErr(fmt.Errorf("unable to forge PublicKey for remote cluster %q: %w", o.ClusterID, err)) + return err + } + + opts.Printer.CheckErr(o.output(pubKey)) + return nil +} diff --git a/pkg/liqoctl/rest/publickey/get.go b/pkg/liqoctl/rest/publickey/get.go new file mode 100644 index 0000000000..ba92460f2c --- /dev/null +++ b/pkg/liqoctl/rest/publickey/get.go @@ -0,0 +1,28 @@ +// Copyright 2019-2023 The Liqo 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. + +package publickey + +import ( + "context" + + "github.com/spf13/cobra" + + "github.com/liqotech/liqo/pkg/liqoctl/rest" +) + +// Get implements the get command. +func (o *Options) Get(_ context.Context, _ *rest.GetOptions) *cobra.Command { + panic("not implemented") +} diff --git a/pkg/liqoctl/rest/publickey/types.go b/pkg/liqoctl/rest/publickey/types.go new file mode 100644 index 0000000000..d3b35fbaef --- /dev/null +++ b/pkg/liqoctl/rest/publickey/types.go @@ -0,0 +1,49 @@ +// Copyright 2019-2023 The Liqo 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. + +package publickey + +import ( + "github.com/liqotech/liqo/pkg/consts" + "github.com/liqotech/liqo/pkg/liqoctl/rest" + argsutils "github.com/liqotech/liqo/pkg/utils/args" +) + +// Options encapsulates the arguments of the gatewayserver command. +type Options struct { + createOptions *rest.CreateOptions + generateOptions *rest.GenerateOptions + + ClusterID string + GatewayName string + GatewayType *argsutils.StringEnum + PublicKey []byte +} + +var _ rest.API = &Options{} + +// PublicKey returns the rest API for the publickey command. +func PublicKey() rest.API { + return &Options{ + GatewayType: argsutils.NewEnumWithVoidDefault([]string{consts.GatewayTypeServer, consts.GatewayTypeClient}), + } +} + +// APIOptions returns the APIOptions for the publickey API. +func (o *Options) APIOptions() *rest.APIOptions { + return &rest.APIOptions{ + EnableCreate: true, + EnableGenerate: true, + } +} diff --git a/pkg/liqoctl/rest/publickey/update.go b/pkg/liqoctl/rest/publickey/update.go new file mode 100644 index 0000000000..10724332f1 --- /dev/null +++ b/pkg/liqoctl/rest/publickey/update.go @@ -0,0 +1,28 @@ +// Copyright 2019-2023 The Liqo 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. + +package publickey + +import ( + "context" + + "github.com/spf13/cobra" + + "github.com/liqotech/liqo/pkg/liqoctl/rest" +) + +// Update implements the update command. +func (o *Options) Update(_ context.Context, _ *rest.UpdateOptions) *cobra.Command { + panic("not implemented") +} diff --git a/pkg/liqoctl/rest/publickey/utils.go b/pkg/liqoctl/rest/publickey/utils.go new file mode 100644 index 0000000000..e5a69234f6 --- /dev/null +++ b/pkg/liqoctl/rest/publickey/utils.go @@ -0,0 +1,142 @@ +// Copyright 2019-2023 The Liqo 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. + +package publickey + +import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + networkingv1alpha1 "github.com/liqotech/liqo/apis/networking/v1alpha1" + "github.com/liqotech/liqo/pkg/consts" + liqoutils "github.com/liqotech/liqo/pkg/utils" +) + +// ForgePublicKey forges a PublicKey. +func ForgePublicKey(name, namespace, remoteClusterID string, key []byte) (*networkingv1alpha1.PublicKey, error) { + pubKey := &networkingv1alpha1.PublicKey{ + TypeMeta: metav1.TypeMeta{ + Kind: networkingv1alpha1.PublicKeyKind, + APIVersion: networkingv1alpha1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Labels: map[string]string{ + consts.RemoteClusterID: remoteClusterID, + }, + }, + } + err := MutatePublicKey(pubKey, remoteClusterID, key) + if err != nil { + return nil, err + } + return pubKey, nil +} + +// MutatePublicKey mutates a PublicKey. +func MutatePublicKey(pubKey *networkingv1alpha1.PublicKey, remoteClusterID string, key []byte) error { + pubKey.Kind = networkingv1alpha1.PublicKeyKind + pubKey.APIVersion = networkingv1alpha1.GroupVersion.String() + + if pubKey.Labels == nil { + pubKey.Labels = make(map[string]string) + } + + pubKey.Labels[consts.RemoteClusterID] = remoteClusterID + pubKey.Labels[consts.GatewayResourceLabel] = consts.GatewayResourceLabelValue + + pubKey.Spec.PublicKey = key + + return nil +} + +// ExtractKeyFromSecretRef extracts the public key data of a secret from a secret reference. +func ExtractKeyFromSecretRef(ctx context.Context, cl client.Client, secretRef *corev1.ObjectReference) ([]byte, error) { + var secret corev1.Secret + if err := cl.Get(ctx, client.ObjectKey{Name: secretRef.Name, Namespace: secretRef.Namespace}, &secret); err != nil { + return nil, err + } + key, ok := secret.Data[consts.PublicKeyField] + if !ok { + return nil, fmt.Errorf("secret %q does not contain %s field", client.ObjectKeyFromObject(&secret), consts.PublicKeyField) + } + return key, nil +} + +// ForgePublicKeyForRemoteCluster forges a PublicKey to be applied on a remote cluster. +func ForgePublicKeyForRemoteCluster(ctx context.Context, cl client.Client, + liqoNamespace, namespace, gatewayName, gatewayType string) (*networkingv1alpha1.PublicKey, error) { + clusterIdentity, err := liqoutils.GetClusterIdentityWithControllerClient(ctx, cl, liqoNamespace) + if err != nil { + return nil, fmt.Errorf("unable to get cluster identity: %w", err) + } + + pubKey := &networkingv1alpha1.PublicKey{ + TypeMeta: metav1.TypeMeta{ + Kind: networkingv1alpha1.PublicKeyKind, + APIVersion: networkingv1alpha1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: clusterIdentity.ClusterName, + Labels: map[string]string{ + consts.RemoteClusterID: clusterIdentity.ClusterID, + consts.GatewayResourceLabel: consts.GatewayResourceLabelValue, + }, + }, + } + + if namespace != "" && namespace != corev1.NamespaceDefault { + pubKey.Namespace = namespace + } + + // Get public keys of the gateway form the secret reference. + secretRef, err := GetGatewaySecretReference(ctx, cl, namespace, gatewayName, gatewayType) + if err != nil { + return nil, err + } + key, err := ExtractKeyFromSecretRef(ctx, cl, secretRef) + if err != nil { + return nil, err + } + pubKey.Spec = networkingv1alpha1.PublicKeySpec{ + PublicKey: key, + } + + return pubKey, nil +} + +// GetGatewaySecretReference returns the secret reference of a gateway. +func GetGatewaySecretReference(ctx context.Context, cl client.Client, namespace, gatewayName, gatewayType string) (*corev1.ObjectReference, error) { + switch gatewayType { + case consts.GatewayTypeServer: + var gwServer networkingv1alpha1.GatewayServer + if err := cl.Get(ctx, client.ObjectKey{Namespace: namespace, Name: gatewayName}, &gwServer); err != nil { + return nil, err + } + return gwServer.Status.SecretRef, nil + case consts.GatewayTypeClient: + var gwClient networkingv1alpha1.GatewayClient + if err := cl.Get(ctx, client.ObjectKey{Namespace: namespace, Name: gatewayName}, &gwClient); err != nil { + return nil, err + } + return gwClient.Status.SecretRef, nil + default: + return nil, fmt.Errorf("unable to forge PublicKey: invalid gateway type %q", gatewayType) + } +} diff --git a/pkg/liqoctl/wait/wait.go b/pkg/liqoctl/wait/wait.go index 59dbae3a98..67fbacc343 100644 --- a/pkg/liqoctl/wait/wait.go +++ b/pkg/liqoctl/wait/wait.go @@ -222,3 +222,60 @@ func (w *Waiter) ForConfiguration(ctx context.Context, conf *networkingv1alpha1. s.Success("Configuration applied successfully") return nil } + +// ForGatewayServerStatusEndpoint waits until the service of a Gateway resource has been created +// (i.e., until its endpoint status is not set). +func (w *Waiter) ForGatewayServerStatusEndpoint(ctx context.Context, gwServer *networkingv1alpha1.GatewayServer) error { + s := w.Printer.StartSpinner("Waiting for gateway server Service to be created") + err := wait.PollUntilContextCancel(ctx, 1*time.Second, true, func(ctx context.Context) (done bool, err error) { + err = w.CRClient.Get(ctx, client.ObjectKey{Name: gwServer.Name, Namespace: gwServer.Namespace}, gwServer) + if err != nil { + return false, client.IgnoreNotFound(err) + } + return gwServer.Status.Endpoint != nil, nil + }) + if err != nil { + s.Fail(fmt.Sprintf("Failed waiting for gateway server Service to be created: %s", output.PrettyErr(err))) + return err + } + s.Success("Gateway server Service created successfully") + return nil +} + +// ForGatewayServerSecretRef waits until the secret containing the public key of a gateway server has been created +// (i.e., until its secret reference status is not set). +func (w *Waiter) ForGatewayServerSecretRef(ctx context.Context, gwServer *networkingv1alpha1.GatewayServer) error { + s := w.Printer.StartSpinner("Waiting for gateway server Secret to be created") + err := wait.PollUntilContextCancel(ctx, 1*time.Second, true, func(ctx context.Context) (done bool, err error) { + err = w.CRClient.Get(ctx, client.ObjectKeyFromObject(gwServer), gwServer) + if err != nil { + return false, client.IgnoreNotFound(err) + } + return gwServer.Status.SecretRef != nil, nil + }) + if err != nil { + s.Fail(fmt.Sprintf("Failed waiting for gateway server Secret to be created: %s", output.PrettyErr(err))) + return err + } + s.Success("Gateway server Secret created successfully") + return nil +} + +// ForGatewayClientSecretRef waits until the secret containing the public key of a gateway client has been created +// (i.e., until its secret reference status is not set). +func (w *Waiter) ForGatewayClientSecretRef(ctx context.Context, gwClient *networkingv1alpha1.GatewayClient) error { + s := w.Printer.StartSpinner("Waiting for gateway client Secret to be created") + err := wait.PollUntilContextCancel(ctx, 1*time.Second, true, func(ctx context.Context) (done bool, err error) { + err = w.CRClient.Get(ctx, client.ObjectKeyFromObject(gwClient), gwClient) + if err != nil { + return false, client.IgnoreNotFound(err) + } + return gwClient.Status.SecretRef != nil, nil + }) + if err != nil { + s.Fail(fmt.Sprintf("Failed waiting for gateway client Secret to be created: %s", output.PrettyErr(err))) + return err + } + s.Success("Gateway client Secret created successfully") + return nil +}