Skip to content

Commit

Permalink
feat: allow vcluster pro via vcluster create & delete
Browse files Browse the repository at this point in the history
  • Loading branch information
FabianKramm committed Sep 22, 2023
1 parent a54e5cf commit ba9282d
Show file tree
Hide file tree
Showing 13 changed files with 486 additions and 103 deletions.
180 changes: 180 additions & 0 deletions cmd/vclusterctl/cmd/app/create/pro.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package create

import (
"context"
"fmt"
"strconv"
"time"

clusterv1 "github.com/loft-sh/agentapi/v3/pkg/apis/loft/cluster/v1"
managementv1 "github.com/loft-sh/api/v3/pkg/apis/management/v1"
storagev1 "github.com/loft-sh/api/v3/pkg/apis/storage/v1"
"github.com/loft-sh/loftctl/v3/cmd/loftctl/cmd/create"
proclient "github.com/loft-sh/loftctl/v3/pkg/client"
"github.com/loft-sh/loftctl/v3/pkg/client/helper"
"github.com/loft-sh/loftctl/v3/pkg/client/naming"
"github.com/loft-sh/loftctl/v3/pkg/config"
"github.com/loft-sh/loftctl/v3/pkg/vcluster"
"github.com/loft-sh/log"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client"
)

func DeployProCluster(ctx context.Context, options *Options, proClient proclient.Client, virtualClusterName string, log log.Logger) error {
// determine project & cluster name
var err error
options.Cluster, options.Project, err = helper.SelectProjectOrCluster(proClient, options.Cluster, options.Project, false, log)
if err != nil {
return err
}

virtualClusterNamespace := naming.ProjectNamespace(options.Project)
managementClient, err := proClient.Management()
if err != nil {
return err
}

// make sure there is not existing virtual cluster
var virtualClusterInstance *managementv1.VirtualClusterInstance
virtualClusterInstance, err = managementClient.Loft().ManagementV1().VirtualClusterInstances(virtualClusterNamespace).Get(ctx, virtualClusterName, metav1.GetOptions{})
if err != nil && !kerrors.IsNotFound(err) {
return fmt.Errorf("couldn't retrieve virtual cluster instance: %w", err)
} else if err == nil && !virtualClusterInstance.DeletionTimestamp.IsZero() {
log.Infof("Waiting until virtual cluster is deleted...")

// wait until the virtual cluster instance is deleted
waitErr := wait.PollUntilContextTimeout(ctx, time.Second, config.Timeout(), false, func(ctx context.Context) (done bool, err error) {
virtualClusterInstance, err = managementClient.Loft().ManagementV1().VirtualClusterInstances(virtualClusterNamespace).Get(ctx, virtualClusterName, metav1.GetOptions{})
if err != nil && !kerrors.IsNotFound(err) {
return false, err
} else if err == nil && virtualClusterInstance.DeletionTimestamp != nil {
return false, nil
}

return true, nil
})
if waitErr != nil {
return fmt.Errorf("get virtual cluster instance: %w", err)
}

virtualClusterInstance = nil
} else if kerrors.IsNotFound(err) {
virtualClusterInstance = nil
}

// if the virtual cluster already exists and flag is not set, we terminate
if !options.Upgrade && virtualClusterInstance != nil {
return fmt.Errorf("virtual cluster %s already exists in project %s", virtualClusterName, options.Project)
}

// create virtual cluster if necessary
if virtualClusterInstance == nil {
// create via template

// resolve template
virtualClusterTemplate, resolvedParameters, err := create.ResolveTemplate(
proClient,
options.Project,
options.Template,
options.TemplateVersion,
options.SetParams,
options.Params,
log,
)
if err != nil {
return err
}

// create virtual cluster instance
zone, offset := time.Now().Zone()
virtualClusterInstance = &managementv1.VirtualClusterInstance{
ObjectMeta: metav1.ObjectMeta{
Namespace: naming.ProjectNamespace(options.Project),
Name: virtualClusterName,
Annotations: map[string]string{
clusterv1.SleepModeTimezoneAnnotation: zone + "#" + strconv.Itoa(offset),
},
},
Spec: managementv1.VirtualClusterInstanceSpec{
VirtualClusterInstanceSpec: storagev1.VirtualClusterInstanceSpec{
TemplateRef: &storagev1.TemplateRef{
Name: virtualClusterTemplate.Name,
Version: options.TemplateVersion,
},
ClusterRef: storagev1.VirtualClusterClusterRef{
ClusterRef: storagev1.ClusterRef{
Cluster: options.Cluster,
},
},
Parameters: resolvedParameters,
},
},
}

// set links
create.SetCustomLinksAnnotation(virtualClusterInstance, options.Links)

// create virtualclusterinstance
log.Infof("Creating virtual cluster %s in project %s with template %s...", virtualClusterName, options.Project, virtualClusterTemplate.Name)
virtualClusterInstance, err = managementClient.Loft().ManagementV1().VirtualClusterInstances(virtualClusterInstance.Namespace).Create(ctx, virtualClusterInstance, metav1.CreateOptions{})
if err != nil {
return fmt.Errorf("create virtual cluster: %w", err)
}
} else if options.Upgrade {
// resolve template
virtualClusterTemplate, resolvedParameters, err := create.ResolveTemplate(
proClient,
options.Project,
options.Template,
options.TemplateVersion,
options.SetParams,
options.Params,
log,
)
if err != nil {
return err
}

// update virtual cluster instance
if virtualClusterInstance.Spec.TemplateRef == nil {
return fmt.Errorf("virtual cluster instance doesn't use a template, cannot update virtual cluster")
}

oldVirtualCluster := virtualClusterInstance.DeepCopy()
templateRefChanged := virtualClusterInstance.Spec.TemplateRef.Name != virtualClusterTemplate.Name
paramsChanged := virtualClusterInstance.Spec.Parameters != resolvedParameters
versionChanged := (options.TemplateVersion != "" && virtualClusterInstance.Spec.TemplateRef.Version != options.TemplateVersion)
linksChanged := create.SetCustomLinksAnnotation(virtualClusterInstance, options.Links)

// check if update is needed
if templateRefChanged || paramsChanged || versionChanged || linksChanged {
virtualClusterInstance.Spec.TemplateRef.Name = virtualClusterTemplate.Name
virtualClusterInstance.Spec.TemplateRef.Version = options.TemplateVersion
virtualClusterInstance.Spec.Parameters = resolvedParameters

patch := client.MergeFrom(oldVirtualCluster)
patchData, err := patch.Data(virtualClusterInstance)
if err != nil {
return fmt.Errorf("calculate update patch: %w", err)
}
log.Infof("Updating virtual cluster %s in project %s...", virtualClusterName, options.Project)
virtualClusterInstance, err = managementClient.Loft().ManagementV1().VirtualClusterInstances(virtualClusterInstance.Namespace).Patch(ctx, virtualClusterInstance.Name, patch.Type(), patchData, metav1.PatchOptions{})
if err != nil {
return fmt.Errorf("patch virtual cluster: %w", err)
}
} else {
log.Infof("Skip updating virtual cluster...")
}
}

// wait until virtual cluster is ready
virtualClusterInstance, err = vcluster.WaitForVirtualClusterInstance(ctx, managementClient, virtualClusterInstance.Namespace, virtualClusterInstance.Name, true, log)
if err != nil {
return err
}
log.Donef("Successfully created the virtual cluster %s in project %s", virtualClusterName, options.Project)

return nil
}
23 changes: 16 additions & 7 deletions cmd/vclusterctl/cmd/app/create/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,33 @@ type Options struct {
ChartName string
ChartRepo string
LocalChartDir string
K3SImage string
Distro string
CIDR string
ExtraValues []string
Values []string
SetValues []string
DeprecatedExtraValues []string

KubernetesVersion string

CreateNamespace bool
DisableIngressSync bool
CreateClusterRole bool
UpdateCurrent bool
Expose bool
ExposeLocal bool

Connect bool
Upgrade bool
Isolate bool
ReleaseValues string
Connect bool
Upgrade bool
Isolate bool

// Pro
Project string
Cluster string
Template string
TemplateVersion string
Links []string
Params string
SetParams []string
DisablePro bool
}

type Values struct {
Expand Down
Loading

0 comments on commit ba9282d

Please sign in to comment.