diff --git a/cmd/vclusterctl/cmd/platform/add/cluster.go b/cmd/vclusterctl/cmd/platform/add/cluster.go index 8b34811813..bec88b9a87 100644 --- a/cmd/vclusterctl/cmd/platform/add/cluster.go +++ b/cmd/vclusterctl/cmd/platform/add/cluster.go @@ -70,7 +70,7 @@ vcluster platform add cluster my-cluster }, } - c.Flags().StringVar(&cmd.Namespace, "namespace", "loft", "The namespace to generate the service account in. The namespace will be created if it does not exist") + c.Flags().StringVar(&cmd.Namespace, "namespace", clihelper.DefaultPlatformNamespace, "The namespace to generate the service account in. The namespace will be created if it does not exist") c.Flags().StringVar(&cmd.ServiceAccount, "service-account", "loft-admin", "The service account name to create") c.Flags().StringVar(&cmd.DisplayName, "display-name", "", "The display name to show in the UI for this cluster") c.Flags().BoolVar(&cmd.Wait, "wait", false, "If true, will wait until the cluster is initialized") @@ -121,8 +121,9 @@ func (cmd *ClusterCmd) Run(ctx context.Context, args []string) error { User: user, Team: team, }, - NetworkPeer: true, - Access: getAccess(user, team), + NetworkPeer: true, + ManagementNamespace: cmd.Namespace, + Access: getAccess(user, team), }, }, }, metav1.CreateOptions{}) @@ -130,6 +131,21 @@ func (cmd *ClusterCmd) Run(ctx context.Context, args []string) error { return fmt.Errorf("create cluster: %w", err) } + // get namespace to install if cluster already exists + if kerrors.IsAlreadyExists(err) { + cluster, err := managementClient.Loft().ManagementV1().Clusters().Get(ctx, clusterName, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("get cluster: %w", err) + } + + cmd.Namespace = cluster.Spec.ManagementNamespace + if cmd.Namespace == "" { + cmd.Namespace = "loft" // since this is hardcoded in the platform at https://github.com/loft-sh/loft-enterprise/blob/b716f86a83d5f037ad993a0c3467b54393ef3b1f/pkg/util/agenthelper/helper.go#L9 + } + + cmd.Log.Infof("Using namespace %s because cluster already exists", cmd.Namespace) + } + accessKey, err := managementClient.Loft().ManagementV1().Clusters().GetAccessKey(ctx, clusterName, metav1.GetOptions{}) if err != nil { return fmt.Errorf("get cluster access key: %w", err) diff --git a/cmd/vclusterctl/cmd/platform/backup/management.go b/cmd/vclusterctl/cmd/platform/backup/management.go index 25ed9add97..cc7b6504b8 100644 --- a/cmd/vclusterctl/cmd/platform/backup/management.go +++ b/cmd/vclusterctl/cmd/platform/backup/management.go @@ -1,6 +1,7 @@ package backup import ( + "context" "fmt" "os" @@ -67,8 +68,10 @@ vcluster platform backup management }, } + platformNamespace, _ := clihelper.VClusterPlatformInstallationNamespace(context.Background()) + c.Flags().StringSliceVar(&cmd.Skip, "skip", []string{}, "What resources the backup should skip. Valid options are: users, teams, accesskeys, sharedsecrets, clusters and clusteraccounttemplates") - c.Flags().StringVar(&cmd.Namespace, "namespace", "vcluster-platform", product.Replace("The namespace vCluster platform was installed into")) + c.Flags().StringVar(&cmd.Namespace, "namespace", platformNamespace, product.Replace("The namespace vCluster platform was installed into")) c.Flags().StringVar(&cmd.Filename, "filename", "backup.yaml", "The filename to write the backup to") return c } diff --git a/cmd/vclusterctl/cmd/platform/get/secret.go b/cmd/vclusterctl/cmd/platform/get/secret.go index bca1a1e6e7..18e8821bfe 100644 --- a/cmd/vclusterctl/cmd/platform/get/secret.go +++ b/cmd/vclusterctl/cmd/platform/get/secret.go @@ -14,6 +14,7 @@ import ( "github.com/loft-sh/vcluster/pkg/cli/flags" "github.com/loft-sh/vcluster/pkg/cli/util" "github.com/loft-sh/vcluster/pkg/platform" + "github.com/loft-sh/vcluster/pkg/platform/clihelper" pdefaults "github.com/loft-sh/vcluster/pkg/platform/defaults" "github.com/loft-sh/vcluster/pkg/projectutil" "github.com/pkg/errors" @@ -111,7 +112,7 @@ func (cmd *SecretCmd) Run(ctx context.Context, args []string) error { case set.ProjectSecret: namespace = projectutil.ProjectNamespace(cmd.Project) case set.SharedSecret: - namespace, err = set.GetSharedSecretNamespace(cmd.Namespace) + namespace, err = clihelper.VClusterPlatformInstallationNamespace(ctx) if err != nil { return errors.Wrap(err, "get shared secrets namespace") } diff --git a/cmd/vclusterctl/cmd/platform/reset.go b/cmd/vclusterctl/cmd/platform/reset.go index 6c9c4d2d54..a2ac771721 100644 --- a/cmd/vclusterctl/cmd/platform/reset.go +++ b/cmd/vclusterctl/cmd/platform/reset.go @@ -10,6 +10,7 @@ import ( "github.com/loft-sh/log" "github.com/loft-sh/log/survey" "github.com/loft-sh/vcluster/pkg/cli/flags" + "github.com/loft-sh/vcluster/pkg/platform/clihelper" "github.com/loft-sh/vcluster/pkg/platform/kube" "github.com/loft-sh/vcluster/pkg/platform/random" "github.com/pkg/errors" @@ -79,7 +80,7 @@ vcluster platform reset password --user admin c.Flags().StringVar(&cmd.Password, "password", "", "The new password to use") c.Flags().BoolVar(&cmd.Create, "create", false, "Creates the user if it does not exist") c.Flags().BoolVar(&cmd.Force, "force", false, "If user had no password will create one") - c.Flags().StringVar(&cmd.Namespace, "namespace", "vcluster-platform", "The namespace to use") + c.Flags().StringVar(&cmd.Namespace, "namespace", clihelper.DefaultPlatformNamespace, "The namespace to use") return c } @@ -107,6 +108,15 @@ func (cmd *PasswordCmd) Run() error { return fmt.Errorf("user %s was not found, run with '--create' to create this user automatically", cmd.User) } + if cmd.Namespace == "" { + namespace, err := clihelper.VClusterPlatformInstallationNamespace(context.Background()) + if err != nil { + return fmt.Errorf("failed to find platform namespace") + } + + cmd.Namespace = namespace + } + user, err = managementClient.Loft().StorageV1().Users().Create(context.Background(), &storagev1.User{ ObjectMeta: metav1.ObjectMeta{ Name: cmd.User, diff --git a/cmd/vclusterctl/cmd/platform/set/secret.go b/cmd/vclusterctl/cmd/platform/set/secret.go index 3f9b9c5ce5..ec34fb1eb2 100644 --- a/cmd/vclusterctl/cmd/platform/set/secret.go +++ b/cmd/vclusterctl/cmd/platform/set/secret.go @@ -13,6 +13,7 @@ import ( "github.com/loft-sh/vcluster/pkg/cli/flags" "github.com/loft-sh/vcluster/pkg/cli/util" "github.com/loft-sh/vcluster/pkg/platform" + "github.com/loft-sh/vcluster/pkg/platform/clihelper" pdefaults "github.com/loft-sh/vcluster/pkg/platform/defaults" "github.com/loft-sh/vcluster/pkg/platform/kube" "github.com/loft-sh/vcluster/pkg/projectutil" @@ -112,7 +113,7 @@ func (cmd *SecretCmd) Run(cobraCmd *cobra.Command, args []string) error { namespace := projectutil.ProjectNamespace(cmd.Project) return cmd.setProjectSecret(ctx, managementClient, args, namespace, secretName, keyName) case SharedSecret: - namespace, err := GetSharedSecretNamespace(cmd.Namespace) + namespace, err := clihelper.VClusterPlatformInstallationNamespace(ctx) if err != nil { return errors.Wrap(err, "get shared secrets namespace") } @@ -258,11 +259,3 @@ func (cmd *SecretCmd) setSharedSecret(ctx context.Context, managementClient kube cmd.log.Donef("Successfully set secret key %s.%s", secretName, keyName) return nil } - -func GetSharedSecretNamespace(namespace string) (string, error) { - if namespace == "" { - namespace = "loft" - } - - return namespace, nil -} diff --git a/cmd/vclusterctl/cmd/platform/start.go b/cmd/vclusterctl/cmd/platform/start.go index 6c29119452..74ecbe7172 100644 --- a/cmd/vclusterctl/cmd/platform/start.go +++ b/cmd/vclusterctl/cmd/platform/start.go @@ -13,6 +13,7 @@ import ( "github.com/loft-sh/vcluster/pkg/cli/flags" "github.com/loft-sh/vcluster/pkg/cli/start" "github.com/loft-sh/vcluster/pkg/platform" + "github.com/loft-sh/vcluster/pkg/platform/clihelper" "github.com/spf13/cobra" "k8s.io/client-go/tools/clientcmd" ) @@ -55,7 +56,7 @@ before running this command: } startCmd.Flags().StringVar(&cmd.Context, "context", "", "The kube context to use for installation") - startCmd.Flags().StringVar(&cmd.Namespace, "namespace", "vcluster-platform", "The namespace to install vCluster platform into") + startCmd.Flags().StringVar(&cmd.Namespace, "namespace", clihelper.DefaultPlatformNamespace, "The namespace to install vCluster platform into") startCmd.Flags().StringVar(&cmd.LocalPort, "local-port", "", "The local port to bind to if using port-forwarding") startCmd.Flags().StringVar(&cmd.Host, "host", "", "Provide a hostname to enable ingress and configure its hostname") startCmd.Flags().StringVar(&cmd.Password, "password", "", "The password to use for the admin account. (If empty this will be the namespace UID)") diff --git a/pkg/cli/start/success.go b/pkg/cli/start/success.go index 9b6844b202..68ea89b478 100644 --- a/pkg/cli/start/success.go +++ b/pkg/cli/start/success.go @@ -239,7 +239,7 @@ func (l *LoftStarter) waitForLoft(ctx context.Context) (*corev1.Pod, error) { } // ensure user admin secret is there - isNewPassword, err := clihelper.EnsureAdminPassword(ctx, l.KubeClient, l.RestConfig, l.Password, l.Log) + isNewPassword, err := clihelper.EnsureAdminPassword(ctx, l.KubeClient, l.RestConfig, l.Namespace, l.Password, l.Log) if err != nil { return nil, err } diff --git a/pkg/platform/clihelper/clihelper.go b/pkg/platform/clihelper/clihelper.go index 9db52adb0a..3e13c20653 100644 --- a/pkg/platform/clihelper/clihelper.go +++ b/pkg/platform/clihelper/clihelper.go @@ -35,6 +35,7 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" "k8s.io/client-go/transport/spdy" "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset" @@ -56,6 +57,8 @@ const defaultReleaseName = "loft" const LoftRouterDomainSecret = "loft-router-domain" +const DefaultPlatformNamespace = "vcluster-platform" + const defaultTimeout = 10 * time.Minute const timeoutEnvVariable = "LOFT_TIMEOUT" @@ -434,6 +437,15 @@ func IsLoftAlreadyInstalled(ctx context.Context, kubeClient kubernetes.Interface return false, errors.New("nil kubeClient") } + if namespace == "" { + var nsErr error + namespace, nsErr = VClusterPlatformInstallationNamespace(ctx) + + if nsErr != nil { + return false, nil + } + } + _, err := kubeClient.AppsV1().Deployments(namespace).Get(ctx, defaultDeploymentName, metav1.GetOptions{}) if err != nil { if kerrors.IsNotFound(err) { @@ -446,6 +458,39 @@ func IsLoftAlreadyInstalled(ctx context.Context, kubeClient kubernetes.Interface return true, nil } +func VClusterPlatformInstallationNamespace(ctx context.Context) (string, error) { + kubeClientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(clientcmd.NewDefaultClientConfigLoadingRules(), &clientcmd.ConfigOverrides{}) + + kubeConfig, err := kubeClientConfig.ClientConfig() + if err != nil { + return "", fmt.Errorf("there is an error loading your current kube config (%w), please make sure you have access to a kubernetes cluster and the command `kubectl get namespaces` is working", err) + } + + kubeClient, err := kubernetes.NewForConfig(kubeConfig) + if err != nil { + return "", fmt.Errorf("there is an error loading your current kube config (%w), please make sure you have access to a kubernetes cluster and the command `kubectl get namespaces` is working", err) + } + + deployments, err := kubeClient.AppsV1().Deployments(metav1.NamespaceAll).List(ctx, metav1.ListOptions{ + LabelSelector: "app=loft", + }) + if err != nil { + if kerrors.IsNotFound(err) { + return "", nil + } + + return "", fmt.Errorf("error accessing kubernetes cluster: %w", err) + } + + for _, deploy := range deployments.Items { + if deploy.Name == defaultDeploymentName { + return deploy.Namespace, nil + } + } + + return "", fmt.Errorf("failed to find the namespace loft is installed in") +} + func UninstallLoft(ctx context.Context, kubeClient kubernetes.Interface, restConfig *rest.Config, kubeContext, namespace string, log log.Logger) error { if kubeClient == nil { return errors.New("nil kubeClient") @@ -755,7 +800,7 @@ func getHelmWorkdir(chartName string) (string, error) { // Makes sure that admin user and password secret exists // Returns (true, nil) if everything is correct but password is different from parameter `password` -func EnsureAdminPassword(ctx context.Context, kubeClient kubernetes.Interface, restConfig *rest.Config, password string, log log.Logger) (bool, error) { +func EnsureAdminPassword(ctx context.Context, kubeClient kubernetes.Interface, restConfig *rest.Config, namespace, password string, log log.Logger) (bool, error) { if restConfig == nil { return false, errors.New("nil kubeClient") } @@ -783,7 +828,7 @@ func EnsureAdminPassword(ctx context.Context, kubeClient kubernetes.Interface, r Groups: []string{"system:masters"}, PasswordRef: &storagev1.SecretRef{ SecretName: "loft-user-secret-admin", - SecretNamespace: "loft", + SecretNamespace: namespace, Key: "password", }, },