From 1867c0199385c6868c41c1f2b54d7fc20bab8638 Mon Sep 17 00:00:00 2001 From: Alessandro Olivero Date: Mon, 7 Aug 2023 16:57:44 +0200 Subject: [PATCH] liqoctl: add create VirtualNode command --- cmd/liqoctl/cmd/root.go | 10 + cmd/liqoctl/main.go | 2 + pkg/identityManager/certificate.go | 2 +- pkg/identityManager/const.go | 7 +- pkg/identityManager/identityManager_test.go | 2 +- pkg/liqoctl/completion/completion.go | 78 +++++++ pkg/liqoctl/create/doc.go | 16 ++ pkg/liqoctl/create/types.go | 51 +++++ pkg/liqoctl/delete/doc.go | 16 ++ pkg/liqoctl/delete/types.go | 51 +++++ pkg/liqoctl/rest/doc.go | 16 ++ pkg/liqoctl/rest/types.go | 68 +++++++ pkg/liqoctl/rest/virtualnode/create.go | 215 ++++++++++++++++++++ pkg/liqoctl/rest/virtualnode/delete.go | 79 +++++++ pkg/liqoctl/rest/virtualnode/doc.go | 16 ++ pkg/liqoctl/rest/virtualnode/get.go | 28 +++ pkg/liqoctl/rest/virtualnode/types.go | 52 +++++ pkg/liqoctl/rest/virtualnode/update.go | 28 +++ 18 files changed, 732 insertions(+), 5 deletions(-) create mode 100644 pkg/liqoctl/create/doc.go create mode 100644 pkg/liqoctl/create/types.go create mode 100644 pkg/liqoctl/delete/doc.go create mode 100644 pkg/liqoctl/delete/types.go create mode 100644 pkg/liqoctl/rest/doc.go create mode 100644 pkg/liqoctl/rest/types.go create mode 100644 pkg/liqoctl/rest/virtualnode/create.go create mode 100644 pkg/liqoctl/rest/virtualnode/delete.go create mode 100644 pkg/liqoctl/rest/virtualnode/doc.go create mode 100644 pkg/liqoctl/rest/virtualnode/get.go create mode 100644 pkg/liqoctl/rest/virtualnode/types.go create mode 100644 pkg/liqoctl/rest/virtualnode/update.go diff --git a/cmd/liqoctl/cmd/root.go b/cmd/liqoctl/cmd/root.go index 07de76b1fb..4fe4ca0908 100644 --- a/cmd/liqoctl/cmd/root.go +++ b/cmd/liqoctl/cmd/root.go @@ -30,11 +30,19 @@ import ( "k8s.io/klog/v2" "k8s.io/kubectl/pkg/cmd/util" + "github.com/liqotech/liqo/pkg/liqoctl/create" + "github.com/liqotech/liqo/pkg/liqoctl/delete" "github.com/liqotech/liqo/pkg/liqoctl/factory" + "github.com/liqotech/liqo/pkg/liqoctl/rest" + "github.com/liqotech/liqo/pkg/liqoctl/rest/virtualnode" ) var liqoctl string +var liqoResources = []rest.APIProvider{ + virtualnode.VirtualNode, +} + func init() { liqoctl = os.Args[0] @@ -117,6 +125,8 @@ func NewRootCommand(ctx context.Context) *cobra.Command { cmd.AddCommand(newMoveCommand(ctx, f)) cmd.AddCommand(newVersionCommand(ctx, f)) cmd.AddCommand(newDocsCommand(ctx)) + cmd.AddCommand(create.NewCreateCommand(ctx, liqoResources, f)) + cmd.AddCommand(delete.NewDeleteCommand(ctx, liqoResources, f)) return cmd } diff --git a/cmd/liqoctl/main.go b/cmd/liqoctl/main.go index 5102c0f36b..5b69b74a56 100644 --- a/cmd/liqoctl/main.go +++ b/cmd/liqoctl/main.go @@ -28,6 +28,7 @@ import ( netv1alpha1 "github.com/liqotech/liqo/apis/net/v1alpha1" offloadingv1alpha1 "github.com/liqotech/liqo/apis/offloading/v1alpha1" sharingv1alpha1 "github.com/liqotech/liqo/apis/sharing/v1alpha1" + virtualkubeletv1alpha1 "github.com/liqotech/liqo/apis/virtualkubelet/v1alpha1" liqocmd "github.com/liqotech/liqo/cmd/liqoctl/cmd" ) @@ -36,6 +37,7 @@ func init() { utilruntime.Must(netv1alpha1.AddToScheme(scheme.Scheme)) utilruntime.Must(offloadingv1alpha1.AddToScheme(scheme.Scheme)) utilruntime.Must(sharingv1alpha1.AddToScheme(scheme.Scheme)) + utilruntime.Must(virtualkubeletv1alpha1.AddToScheme(scheme.Scheme)) } func main() { diff --git a/pkg/identityManager/certificate.go b/pkg/identityManager/certificate.go index 7a25bba19b..eb41762d76 100644 --- a/pkg/identityManager/certificate.go +++ b/pkg/identityManager/certificate.go @@ -44,7 +44,7 @@ func (certManager *identityManager) StoreIdentity(ctx context.Context, remoteClu Labels: map[string]string{ localIdentitySecretLabel: "true", discovery.ClusterIDLabel: remoteCluster.ClusterID, - certificateAvailableLabel: "true", + CertificateAvailableLabel: "true", }, Annotations: map[string]string{ // one year starting from now diff --git a/pkg/identityManager/const.go b/pkg/identityManager/const.go index 5c6dab1776..49aac31649 100644 --- a/pkg/identityManager/const.go +++ b/pkg/identityManager/const.go @@ -17,9 +17,10 @@ package identitymanager const defaultOrganization = "liqo.io" const ( - localIdentitySecretLabel = "discovery.liqo.io/local-identity" - remoteTenantCSRLabel = "discovery.liqo.io/remote-tenant-csr" - certificateAvailableLabel = "discovery.liqo.io/certificate-available" + localIdentitySecretLabel = "discovery.liqo.io/local-identity" + remoteTenantCSRLabel = "discovery.liqo.io/remote-tenant-csr" + // CertificateAvailableLabel is the label used to identify the secrets containing a certificate. + CertificateAvailableLabel = "discovery.liqo.io/certificate-available" ) const ( diff --git a/pkg/identityManager/identityManager_test.go b/pkg/identityManager/identityManager_test.go index 58e70da504..2d1c85b558 100644 --- a/pkg/identityManager/identityManager_test.go +++ b/pkg/identityManager/identityManager_test.go @@ -106,7 +106,7 @@ var _ = Describe("IdentityManager", func() { commonSecretChecks := func(secret *v1.Secret) { Expect(secret.Namespace).To(Equal(namespace.Name)) Expect(secret.GetLabels()).To(HaveKeyWithValue(localIdentitySecretLabel, "true")) - Expect(secret.GetLabels()).To(HaveKeyWithValue(certificateAvailableLabel, "true")) + Expect(secret.GetLabels()).To(HaveKeyWithValue(CertificateAvailableLabel, "true")) Expect(secret.GetLabels()).To(HaveKeyWithValue(discovery.ClusterIDLabel, remoteCluster.ClusterID)) Expect(secret.GetAnnotations()).To(HaveKey(certificateExpireTimeAnnotation)) Expect(secret.Data[privateKeySecretKey]).To(Equal(key)) diff --git a/pkg/liqoctl/completion/completion.go b/pkg/liqoctl/completion/completion.go index 0aadf7c2df..6eae80446f 100644 --- a/pkg/liqoctl/completion/completion.go +++ b/pkg/liqoctl/completion/completion.go @@ -24,6 +24,8 @@ import ( discoveryv1alpha1 "github.com/liqotech/liqo/apis/discovery/v1alpha1" offloadingv1alpha1 "github.com/liqotech/liqo/apis/offloading/v1alpha1" + virtualkubeletv1alpha1 "github.com/liqotech/liqo/apis/virtualkubelet/v1alpha1" + identitymanager "github.com/liqotech/liqo/pkg/identityManager" "github.com/liqotech/liqo/pkg/liqoctl/factory" "github.com/liqotech/liqo/pkg/utils/slice" ) @@ -123,6 +125,24 @@ func Nodes(ctx context.Context, f *factory.Factory, argsLimit int) FnType { return common(ctx, f, argsLimit, retriever) } +// VirtualNodes returns a function to autocomplete virtual node names. +func VirtualNodes(ctx context.Context, f *factory.Factory, argsLimit int) FnType { + retriever := func(ctx context.Context, f *factory.Factory) ([]string, error) { + var virtualNodes virtualkubeletv1alpha1.VirtualNodeList + if err := f.CRClient.List(ctx, &virtualNodes, client.InNamespace(f.Namespace)); err != nil { + return nil, err + } + + var names []string + for i := range virtualNodes.Items { + names = append(names, virtualNodes.Items[i].Name) + } + return names, nil + } + + return common(ctx, f, argsLimit, retriever) +} + // ForeignClusters returns a function to autocomplete ForeignCluster names. func ForeignClusters(ctx context.Context, f *factory.Factory, argsLimit int) FnType { retriever := func(ctx context.Context, f *factory.Factory) ([]string, error) { @@ -141,6 +161,64 @@ func ForeignClusters(ctx context.Context, f *factory.Factory, argsLimit int) FnT return common(ctx, f, argsLimit, retriever) } +// ClusterIDs returns a function to autocomplete ForeignCluster cluster IDs. +func ClusterIDs(ctx context.Context, f *factory.Factory, argsLimit int) FnType { + retriever := func(ctx context.Context, f *factory.Factory) ([]string, error) { + var foreignClusters discoveryv1alpha1.ForeignClusterList + if err := f.CRClient.List(ctx, &foreignClusters); err != nil { + return nil, err + } + + var ids []string + for i := range foreignClusters.Items { + ids = append(ids, foreignClusters.Items[i].Spec.ClusterIdentity.ClusterID) + } + return ids, nil + } + + return common(ctx, f, argsLimit, retriever) +} + +// ClusterNames returns a function to autocomplete ForeignCluster cluster names. +func ClusterNames(ctx context.Context, f *factory.Factory, argsLimit int) FnType { + retriever := func(ctx context.Context, f *factory.Factory) ([]string, error) { + var foreignClusters discoveryv1alpha1.ForeignClusterList + if err := f.CRClient.List(ctx, &foreignClusters); err != nil { + return nil, err + } + + var names []string + for i := range foreignClusters.Items { + names = append(names, foreignClusters.Items[i].Spec.ClusterIdentity.ClusterName) + } + return names, nil + } + + return common(ctx, f, argsLimit, retriever) +} + +// KubeconfigSecretNames returns a function to autocomplete kubeconfig secret names. +func KubeconfigSecretNames(ctx context.Context, f *factory.Factory, argsLimit int) FnType { + retriever := func(ctx context.Context, f *factory.Factory) ([]string, error) { + matchingLabels := client.MatchingLabels{ + identitymanager.CertificateAvailableLabel: "true", + } + + var secrets corev1.SecretList + if err := f.CRClient.List(ctx, &secrets, client.InNamespace(f.Namespace), matchingLabels); err != nil { + return nil, err + } + + var names []string + for i := range secrets.Items { + names = append(names, secrets.Items[i].Name) + } + return names, nil + } + + return common(ctx, f, argsLimit, retriever) +} + // PVCs returns a function to autocomplete PVC names. func PVCs(ctx context.Context, f *factory.Factory, argsLimit int) FnType { retriever := func(ctx context.Context, f *factory.Factory) ([]string, error) { diff --git a/pkg/liqoctl/create/doc.go b/pkg/liqoctl/create/doc.go new file mode 100644 index 0000000000..6ebe9a0b89 --- /dev/null +++ b/pkg/liqoctl/create/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 create contains the implementation of the 'create' command +package create diff --git a/pkg/liqoctl/create/types.go b/pkg/liqoctl/create/types.go new file mode 100644 index 0000000000..8df91db483 --- /dev/null +++ b/pkg/liqoctl/create/types.go @@ -0,0 +1,51 @@ +// 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 create + +import ( + "context" + + "github.com/spf13/cobra" + + "github.com/liqotech/liqo/pkg/liqoctl/factory" + "github.com/liqotech/liqo/pkg/liqoctl/rest" +) + +// NewCreateCommand returns the cobra command for the create subcommand. +func NewCreateCommand(ctx context.Context, liqoResources []rest.APIProvider, f *factory.Factory) *cobra.Command { + options := &rest.CreateOptions{ + Factory: f, + } + + cmd := &cobra.Command{ + Use: "create", + Short: "Create Liqo resources", + Long: "Create Liqo resources.", + Args: cobra.NoArgs, + } + + f.AddNamespaceFlag(cmd.PersistentFlags()) + + for _, r := range liqoResources { + api := r() + + apiOptions := api.APIOptions() + if apiOptions.EnableCreate { + cmd.AddCommand(api.Create(ctx, options)) + } + } + + return cmd +} diff --git a/pkg/liqoctl/delete/doc.go b/pkg/liqoctl/delete/doc.go new file mode 100644 index 0000000000..ca70dc2f8b --- /dev/null +++ b/pkg/liqoctl/delete/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 delete contains the implementation of the 'delete' command +package delete diff --git a/pkg/liqoctl/delete/types.go b/pkg/liqoctl/delete/types.go new file mode 100644 index 0000000000..d91f4df615 --- /dev/null +++ b/pkg/liqoctl/delete/types.go @@ -0,0 +1,51 @@ +// 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 delete + +import ( + "context" + + "github.com/spf13/cobra" + + "github.com/liqotech/liqo/pkg/liqoctl/factory" + "github.com/liqotech/liqo/pkg/liqoctl/rest" +) + +// NewDeleteCommand returns the cobra command for the delete subcommand. +func NewDeleteCommand(ctx context.Context, liqoResources []rest.APIProvider, f *factory.Factory) *cobra.Command { + options := &rest.DeleteOptions{ + Factory: f, + } + + cmd := &cobra.Command{ + Use: "delete", + Short: "Delete Liqo resources", + Long: "Delete Liqo resources.", + Args: cobra.NoArgs, + } + + f.AddNamespaceFlag(cmd.PersistentFlags()) + + for _, r := range liqoResources { + api := r() + + apiOptions := api.APIOptions() + if apiOptions.EnableDelete { + cmd.AddCommand(api.Delete(ctx, options)) + } + } + + return cmd +} diff --git a/pkg/liqoctl/rest/doc.go b/pkg/liqoctl/rest/doc.go new file mode 100644 index 0000000000..cf8f34e2ec --- /dev/null +++ b/pkg/liqoctl/rest/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 rest contains the types and interfaces to interact with the Liqo API. +package rest diff --git a/pkg/liqoctl/rest/types.go b/pkg/liqoctl/rest/types.go new file mode 100644 index 0000000000..4e593b7710 --- /dev/null +++ b/pkg/liqoctl/rest/types.go @@ -0,0 +1,68 @@ +// 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 rest + +import ( + "context" + + "github.com/spf13/cobra" + + "github.com/liqotech/liqo/pkg/liqoctl/factory" +) + +// APIOptions contains the options for the API. +type APIOptions struct { + EnableCreate bool + EnableDelete bool + EnableGet bool + EnableUpdate bool +} + +// CreateOptions contains the options for the create API. +type CreateOptions struct { + *factory.Factory + + OutputFormat string + Name string +} + +// DeleteOptions contains the options for the delete API. +type DeleteOptions struct { + *factory.Factory + + Name string +} + +// GetOptions contains the options for the get API. +type GetOptions struct { + *factory.Factory +} + +// UpdateOptions contains the options for the update API. +type UpdateOptions struct { + *factory.Factory +} + +// API is the interface that must be implemented by the API. +type API interface { + APIOptions() *APIOptions + Create(ctx context.Context, options *CreateOptions) *cobra.Command + Delete(ctx context.Context, options *DeleteOptions) *cobra.Command + Get(ctx context.Context, options *GetOptions) *cobra.Command + Update(ctx context.Context, options *UpdateOptions) *cobra.Command +} + +// APIProvider is the function that returns the API. +type APIProvider func() API diff --git a/pkg/liqoctl/rest/virtualnode/create.go b/pkg/liqoctl/rest/virtualnode/create.go new file mode 100644 index 0000000000..189845127d --- /dev/null +++ b/pkg/liqoctl/rest/virtualnode/create.go @@ -0,0 +1,215 @@ +// 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 virtualnode + +import ( + "context" + "fmt" + "os" + + "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/cli-runtime/pkg/printers" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + sharingv1alpha1 "github.com/liqotech/liqo/apis/sharing/v1alpha1" + virtualkubeletv1alpha1 "github.com/liqotech/liqo/apis/virtualkubelet/v1alpha1" + "github.com/liqotech/liqo/pkg/discovery" + "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/liqoctl/wait" + "github.com/liqotech/liqo/pkg/utils/args" +) + +const liqoctlCreateVirtualNodeLongHelp = `Create a VirtualNode. + +The VirtualNode resource is used to represent a remote cluster in the local cluster. + +Examples: + $ {{ .Executable }} create virtualnode my-cluster --cluster-id my-cluster-id \ + --cluster-name my-cluster-name --kubeconfig-secret-name my-cluster-kubeconfig --namespace my-cluster` + +// Create creates a VirtualNode. +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: "virtualnode", + Aliases: []string{"vn"}, + Short: "Create a virtual node", + Long: liqoctlCreateVirtualNodeLongHelp, + 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 VirtualNode resource, instead of applying it. Supported formats: json, yaml") + + // TODO: check validity of both cluster-id and cluster-name + cmd.Flags().StringVar(&o.remoteClusterIdentity.ClusterID, "cluster-id", "", "The cluster ID of the remote cluster") + cmd.Flags().StringVar(&o.remoteClusterIdentity.ClusterName, "cluster-name", "", "The cluster name of the remote cluster") + cmd.Flags().BoolVar(&o.createNode, "create-node", + true, "Create a node to target the remote cluster (and schedule on it)") + cmd.Flags().StringVar(&o.kubeconfigSecretName, "kubeconfig-secret-name", + "", "The name of the secret containing the kubeconfig of the remote cluster") + cmd.Flags().StringVar(&o.cpu, "cpu", "2", "The amount of CPU available in the virtual node") + cmd.Flags().StringVar(&o.memory, "memory", "4Gi", "The amount of memory available in the virtual node") + cmd.Flags().StringVar(&o.pods, "pods", "110", "The amount of pods available in the virtual node") + cmd.Flags().StringSliceVar(&o.storageClasses, "storage-classes", + []string{}, "The storage classes offered by the remote cluster. The first one will be used as default") + cmd.Flags().StringToStringVar(&o.labels, "labels", map[string]string{}, "The labels to be added to the virtual node") + + runtime.Must(cmd.MarkFlagRequired("cluster-id")) + runtime.Must(cmd.MarkFlagRequired("cluster-name")) + runtime.Must(cmd.MarkFlagRequired("kubeconfig-secret-name")) + + 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("cluster-name", completion.ClusterNames(ctx, + o.createOptions.Factory, completion.NoLimit))) + runtime.Must(cmd.RegisterFlagCompletionFunc("kubeconfig-secret-name", completion.KubeconfigSecretNames(ctx, + o.createOptions.Factory, completion.NoLimit))) + + return cmd +} + +func (o *Options) handleCreate(ctx context.Context) error { + opts := o.createOptions + if opts.OutputFormat != "" { + opts.Printer.CheckErr(o.output()) + return nil + } + + s := opts.Printer.StartSpinner("Creating virtual node") + + virtualNode := o.forgeVirtualNode() + _, err := controllerutil.CreateOrUpdate(ctx, opts.CRClient, virtualNode, func() error { + return o.mutateVirtualNode(virtualNode) + }) + if err != nil { + s.Fail("Unable to create virtual node: %v", output.PrettyErr(err)) + return err + } + s.Success("Virtual node created") + + if virtualNode.Spec.CreateNode != nil && *virtualNode.Spec.CreateNode { + waiter := wait.NewWaiterFromFactory(opts.Factory) + // TODO: we cannot use the cluster identity here + if err := waiter.ForNode(ctx, virtualNode.Spec.ClusterIdentity); err != nil { + return err + } + } + + return nil +} + +func (o *Options) forgeVirtualNode() *virtualkubeletv1alpha1.VirtualNode { + opts := o.createOptions + return &virtualkubeletv1alpha1.VirtualNode{ + TypeMeta: metav1.TypeMeta{ + APIVersion: virtualkubeletv1alpha1.VirtualNodeGroupVersionResource.GroupVersion().String(), + Kind: virtualkubeletv1alpha1.VirtualNodeKind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: opts.Name, + Namespace: opts.Namespace, + }, + } +} + +func (o *Options) mutateVirtualNode(virtualNode *virtualkubeletv1alpha1.VirtualNode) error { + virtualNode.Spec.ClusterIdentity = &o.remoteClusterIdentity + virtualNode.Spec.CreateNode = &o.createNode + virtualNode.Spec.KubeconfigSecretRef = &corev1.LocalObjectReference{ + Name: o.kubeconfigSecretName, + } + + cpuQnt, err := resource.ParseQuantity(o.cpu) + if err != nil { + return fmt.Errorf("unable to parse cpu quantity: %w", err) + } + memoryQnt, err := resource.ParseQuantity(o.memory) + if err != nil { + return fmt.Errorf("unable to parse memory quantity: %w", err) + } + podsQnt, err := resource.ParseQuantity(o.pods) + if err != nil { + return fmt.Errorf("unable to parse pod quantity: %w", err) + } + virtualNode.Spec.ResourceQuota = corev1.ResourceQuotaSpec{ + Hard: corev1.ResourceList{ + corev1.ResourceCPU: cpuQnt, + corev1.ResourceMemory: memoryQnt, + corev1.ResourcePods: podsQnt, + }, + } + + virtualNode.Spec.StorageClasses = make([]sharingv1alpha1.StorageType, len(o.storageClasses)) + for i, storageClass := range o.storageClasses { + sc := sharingv1alpha1.StorageType{ + StorageClassName: storageClass, + } + if i == 0 { + sc.Default = true + } + virtualNode.Spec.StorageClasses[i] = sc + } + + if virtualNode.ObjectMeta.Labels == nil { + virtualNode.ObjectMeta.Labels = make(map[string]string) + } + virtualNode.ObjectMeta.Labels[discovery.ClusterIDLabel] = o.remoteClusterIdentity.ClusterID + virtualNode.Spec.Labels = o.labels + virtualNode.Spec.Labels[discovery.ClusterIDLabel] = o.remoteClusterIdentity.ClusterID + + return nil +} + +// output implements the logic to output the generated VirtualNode resource. +func (o *Options) output() error { + opts := o.createOptions + var printer printers.ResourcePrinter + switch opts.OutputFormat { + case "yaml": + printer = &printers.YAMLPrinter{} + case "json": + printer = &printers.JSONPrinter{} + default: + return fmt.Errorf("unsupported output format %q", opts.OutputFormat) + } + + virtualNode := o.forgeVirtualNode() + if err := o.mutateVirtualNode(virtualNode); err != nil { + return err + } + + return printer.PrintObj(virtualNode, os.Stdout) +} diff --git a/pkg/liqoctl/rest/virtualnode/delete.go b/pkg/liqoctl/rest/virtualnode/delete.go new file mode 100644 index 0000000000..8e8f447e83 --- /dev/null +++ b/pkg/liqoctl/rest/virtualnode/delete.go @@ -0,0 +1,79 @@ +// 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 virtualnode + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + virtualkubeletv1alpha1 "github.com/liqotech/liqo/apis/virtualkubelet/v1alpha1" + "github.com/liqotech/liqo/pkg/liqoctl/completion" + "github.com/liqotech/liqo/pkg/liqoctl/output" + "github.com/liqotech/liqo/pkg/liqoctl/rest" +) + +const liqoctlDeleteVirtualNodeLongHelp = `Delete a virtual node. + +Examples: + $ {{ .Executable }} delete virtualnode my-cluster` + +// Delete deletes a virtual node. +func (o *Options) Delete(ctx context.Context, options *rest.DeleteOptions) *cobra.Command { + o.deleteOptions = options + + cmd := &cobra.Command{ + Use: "virtualnode", + Aliases: []string{"vn"}, + Short: "Delete a virtual node", + Long: liqoctlDeleteVirtualNodeLongHelp, + + Args: cobra.ExactArgs(1), + ValidArgsFunction: completion.VirtualNodes(ctx, o.deleteOptions.Factory, 1), + + PreRun: func(cmd *cobra.Command, args []string) { + options.Name = args[0] + o.deleteOptions = options + }, + + Run: func(cmd *cobra.Command, args []string) { + output.ExitOnErr(o.handleDelete(ctx)) + }, + } + + return cmd +} + +func (o *Options) handleDelete(ctx context.Context) error { + opts := o.deleteOptions + s := opts.Printer.StartSpinner("Deleting virtual node") + + virtualNode := &virtualkubeletv1alpha1.VirtualNode{ + ObjectMeta: metav1.ObjectMeta{ + Name: opts.Name, + Namespace: opts.Namespace, + }, + } + if err := o.deleteOptions.CRClient.Delete(ctx, virtualNode); err != nil { + err = fmt.Errorf("unable to delete virtual node: %w", err) + s.Fail(err) + return err + } + + s.Success("Virtual node deleted") + return nil +} diff --git a/pkg/liqoctl/rest/virtualnode/doc.go b/pkg/liqoctl/rest/virtualnode/doc.go new file mode 100644 index 0000000000..8b57e61c1f --- /dev/null +++ b/pkg/liqoctl/rest/virtualnode/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 virtualnode contains the rest API commands to allow liqoctl to interact with the VirtualNodes. +package virtualnode diff --git a/pkg/liqoctl/rest/virtualnode/get.go b/pkg/liqoctl/rest/virtualnode/get.go new file mode 100644 index 0000000000..446acb62f6 --- /dev/null +++ b/pkg/liqoctl/rest/virtualnode/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 virtualnode + +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/virtualnode/types.go b/pkg/liqoctl/rest/virtualnode/types.go new file mode 100644 index 0000000000..2f7246d252 --- /dev/null +++ b/pkg/liqoctl/rest/virtualnode/types.go @@ -0,0 +1,52 @@ +// 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 virtualnode + +import ( + discoveryv1alpha1 "github.com/liqotech/liqo/apis/discovery/v1alpha1" + "github.com/liqotech/liqo/pkg/liqoctl/rest" +) + +// Options encapsulates the arguments of the virtualnode command. +type Options struct { + createOptions *rest.CreateOptions + deleteOptions *rest.DeleteOptions + + remoteClusterIdentity discoveryv1alpha1.ClusterIdentity + createNode bool + kubeconfigSecretName string + + cpu string + memory string + pods string + + storageClasses []string + labels map[string]string +} + +var _ rest.API = &Options{} + +// VirtualNode returns the rest API for the virtualnode command. +func VirtualNode() rest.API { + return &Options{} +} + +// APIOptions returns the APIOptions for the virtualnode API. +func (o *Options) APIOptions() *rest.APIOptions { + return &rest.APIOptions{ + EnableCreate: true, + EnableDelete: true, + } +} diff --git a/pkg/liqoctl/rest/virtualnode/update.go b/pkg/liqoctl/rest/virtualnode/update.go new file mode 100644 index 0000000000..936671913f --- /dev/null +++ b/pkg/liqoctl/rest/virtualnode/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 virtualnode + +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") +}