Skip to content

Commit

Permalink
kubectl-kcp: intermediate port to logicalcluster.LogicalCluster
Browse files Browse the repository at this point in the history
  • Loading branch information
sttts committed Mar 26, 2022
1 parent 522adaa commit 468b1d5
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 53 deletions.
58 changes: 27 additions & 31 deletions pkg/cliplugins/workspace/plugin/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,15 @@ import (
"fmt"
"net/url"
"path"
"regexp"
"strings"

"github.com/kcp-dev/apimachinery/pkg/logicalcluster"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

virtualcommandoptions "github.com/kcp-dev/kcp/cmd/virtual-workspaces/options"
tenancyhelpers "github.com/kcp-dev/kcp/pkg/apis/tenancy/v1alpha1/helper"
tenancyv1alpha1 "github.com/kcp-dev/kcp/pkg/apis/tenancy/v1alpha1"
tenancyv1beta1 "github.com/kcp-dev/kcp/pkg/apis/tenancy/v1beta1"
tenancyclient "github.com/kcp-dev/kcp/pkg/client/clientset/versioned"
)
Expand All @@ -49,75 +52,62 @@ func getWorkspaceFromInternalName(ctx context.Context, workspaceInternalName str

// getWorkspaceAndBasePath gets the workspace name, org logical cluster name and the base URL for the current
// workspace.
func getWorkspaceAndBasePath(urlPath string) (orgClusterName, workspaceName, basePath string, err error) {
func getWorkspaceAndBasePath(urlPath string) (orgClusterName logicalcluster.LogicalCluster, workspaceName, basePath string, err error) {
// get workspace from current server URL and check it point to an org or the root workspace
serverURL, err := url.Parse(urlPath)
if err != nil {
return "", "", "", err
return logicalcluster.LogicalCluster{}, "", "", err
}

possiblePrefixes := []string{
"/clusters/",
path.Join(virtualcommandoptions.DefaultRootPathPrefix, "workspaces") + "/",
}

var clusterName string
var clusterName logicalcluster.LogicalCluster
for _, prefix := range possiblePrefixes {
clusterIndex := strings.Index(serverURL.Path, prefix)
if clusterIndex < 0 {
continue
}
clusterName = strings.SplitN(serverURL.Path[clusterIndex+len(prefix):], "/", 2)[0]
clusterName = logicalcluster.New(strings.SplitN(serverURL.Path[clusterIndex+len(prefix):], "/", 2)[0])
basePath = serverURL.Path[:clusterIndex]
}

if clusterName == "" {
return "", "", basePath, fmt.Errorf("current cluster URL %s is not pointing to a workspace", serverURL)
if !isValid(clusterName) {
return logicalcluster.LogicalCluster{}, "", basePath, fmt.Errorf("current cluster URL %s is not pointing to a workspace", serverURL)
}

var org string
if clusterName == tenancyhelpers.RootCluster {
orgClusterName = ""
workspaceName = tenancyhelpers.RootCluster
} else if org, workspaceName, err = tenancyhelpers.ParseLogicalClusterName(clusterName); err != nil {
return "", "", "", fmt.Errorf("unable to parse cluster name %s", clusterName)
} else if org == "system:" {
return "", "", "", fmt.Errorf("no workspaces are accessible from %s", clusterName)
} else if org == tenancyhelpers.RootCluster {
orgClusterName = tenancyhelpers.RootCluster
} else {
orgClusterName, err = tenancyhelpers.ParentClusterName(clusterName)
if err != nil {
// should never happen
return "", "", "", fmt.Errorf("unable to derive parent cluster name for %s", clusterName)
}
parent, workspaceName := clusterName.Split()
if parent.String() == "system" {
return logicalcluster.LogicalCluster{}, "", "", fmt.Errorf("no workspaces are accessible from %s", clusterName)
}

return orgClusterName, workspaceName, basePath, nil
return parent, workspaceName, basePath, nil
}

// upToOrg derives the org workspace cluster name to operate on,
// from a given workspace logical cluster name.
func upToOrg(orgClusterName, workspaceName string, always bool) string {
func upToOrg(orgClusterName logicalcluster.LogicalCluster, workspaceName string, always bool) logicalcluster.LogicalCluster {

if orgClusterName == "" && workspaceName == tenancyhelpers.RootCluster {
return tenancyhelpers.RootCluster
if orgClusterName.Empty() && workspaceName == tenancyv1alpha1.RootCluster.String() {
return tenancyv1alpha1.RootCluster
}

if orgClusterName == tenancyhelpers.RootCluster && !always {
return tenancyhelpers.EncodeOrganizationAndClusterWorkspace(tenancyhelpers.RootCluster, workspaceName)
if orgClusterName == tenancyv1alpha1.RootCluster && !always {
return tenancyv1alpha1.RootCluster.Join(workspaceName)
}

return orgClusterName
}

func outputCurrentWorkspaceMessage(orgName, workspacePrettyName, workspaceName string, opts *Options) error {
func outputCurrentWorkspaceMessage(orgName logicalcluster.LogicalCluster, workspacePrettyName, workspaceName string, opts *Options) error {
if workspaceName != "" {
message := fmt.Sprintf("Current workspace is %q", workspacePrettyName)
if workspaceName != workspacePrettyName {
message = fmt.Sprintf("%s (an alias for %q)", message, workspaceName)
}
if orgName != "" {
if !orgName.Empty() {
message = fmt.Sprintf("%s in organization %q", message, orgName)
}
err := write(opts, fmt.Sprintf("%s.\n", message))
Expand All @@ -130,3 +120,9 @@ func write(opts *Options, str string) error {
_, err := opts.Out.Write([]byte(str))
return err
}

var lclusterRegExp = regexp.MustCompile(`^[a-z0-9][a-z0-9-]*[a-z0-9](:[a-z0-9][a-z0-9-]*[a-z0-9])*$`)

func isValid(cluster logicalcluster.LogicalCluster) bool {
return lclusterRegExp.MatchString(cluster.String())
}
36 changes: 14 additions & 22 deletions pkg/cliplugins/workspace/plugin/kubeconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import (
"strings"
"time"

"github.com/kcp-dev/apimachinery/pkg/logicalcluster"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/cli-runtime/pkg/printers"
Expand All @@ -35,7 +37,7 @@ import (
"k8s.io/client-go/tools/clientcmd/api"

virtualcommandoptions "github.com/kcp-dev/kcp/cmd/virtual-workspaces/options"
tenancyhelpers "github.com/kcp-dev/kcp/pkg/apis/tenancy/v1alpha1/helper"
tenancyv1alpha1 "github.com/kcp-dev/kcp/pkg/apis/tenancy/v1alpha1"
tenancyv1beta1 "github.com/kcp-dev/kcp/pkg/apis/tenancy/v1beta1"
tenancyclient "github.com/kcp-dev/kcp/pkg/client/clientset/versioned"
"github.com/kcp-dev/kcp/pkg/virtual/workspaces/registry"
Expand Down Expand Up @@ -110,13 +112,13 @@ func (kc *KubeConfig) UseWorkspace(ctx context.Context, opts *Options, requested
}

var err error
var orgName string
var orgName logicalcluster.LogicalCluster
orgName, requestedWorkspaceName, err = kc.getWorkspace(kcpPreviousWorkspaceContextKey)
if err != nil {
return err
}

if orgName == "" && requestedWorkspaceName == tenancyhelpers.RootCluster {
if orgName.Empty() && requestedWorkspaceName == tenancyv1alpha1.RootCluster.String() {
workspaceIsRoot = true
}

Expand All @@ -126,7 +128,7 @@ func (kc *KubeConfig) UseWorkspace(ctx context.Context, opts *Options, requested
return err
}

if opts.Scope == registry.PersonalScope && orgName != "" && orgName != tenancyhelpers.RootCluster {
if opts.Scope == registry.PersonalScope && !orgName.Empty() && orgName != tenancyv1alpha1.RootCluster {
// If we're in the personal scope, and request a non-organization workspace, the workspaceName
// returned based on the current context URL might be different from the one visible to the end-user
// due to pretty names (== alias) management.
Expand Down Expand Up @@ -222,7 +224,7 @@ func (kc *KubeConfig) CurrentWorkspace(ctx context.Context, opts *Options) error
}

workspacePrettyName := workspaceName
if org != "" {
if !org.Empty() {
workspaceDirectoryRestConfig, err := kc.workspaceDirectoryRestConfigFromCurrentContext(opts, true)
if err != nil {
return err
Expand Down Expand Up @@ -436,7 +438,7 @@ func (kc *KubeConfig) workspaceDirectoryRestConfigWithoutAuth(contextName string
orgClusterName = upToOrg(orgClusterName, workspaceName, alwaysUpToOrg)

// construct virtual workspace URL. This might redirect to another server if the virtual workspace apiserver is running standalone.
serverURL.Path = path.Join(basePath, virtualcommandoptions.DefaultRootPathPrefix, "workspaces", orgClusterName, opts.Scope)
serverURL.Path = path.Join(basePath, virtualcommandoptions.DefaultRootPathPrefix, "workspaces", orgClusterName.String(), opts.Scope)

workspaceDirectoryCluster := contextCluster.DeepCopy()
workspaceDirectoryCluster.Server = serverURL.String()
Expand Down Expand Up @@ -516,34 +518,24 @@ func (kc *KubeConfig) workspaceDirectoryRestConfigFromCurrentContext(opts *Optio
}

// getWorkspace gets the workspace from a kubeconfig context.
func (kc *KubeConfig) getWorkspace(contextName string) (orgName, workspaceName string, err error) {
func (kc *KubeConfig) getWorkspace(contextName string) (orgName logicalcluster.LogicalCluster, workspaceName string, err error) {
serverInfo, err := kc.getServer(contextName)
if err != nil {
return "", "", err
return logicalcluster.LogicalCluster{}, "", err
}

orgClusterName, workspaceName, _, err := getWorkspaceAndBasePath(serverInfo)
if err != nil {
return "", "", err
return logicalcluster.LogicalCluster{}, "", err
}

if orgClusterName == "" {
orgName = ""
} else {
_, orgName, err = tenancyhelpers.ParseLogicalClusterName(orgClusterName)
if err != nil {
return "", "", err
}
}

return orgName, workspaceName, nil
return orgClusterName, workspaceName, err
}

// getCurrentWorkspace gets the current workspace from the kubeconfig.
func (kc *KubeConfig) getCurrentWorkspace(opts *Options) (orgName, workspaceName string, err error) {
func (kc *KubeConfig) getCurrentWorkspace(opts *Options) (orgName logicalcluster.LogicalCluster, workspaceName string, err error) {
currentContextName, err := kc.getCurrentContextName(opts)
if err != nil {
return "", "", err
return logicalcluster.LogicalCluster{}, "", err
}

return kc.getWorkspace(currentContextName)
Expand Down

0 comments on commit 468b1d5

Please sign in to comment.