Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update tanzu context APIs to support ClusterGroup #142

Merged
merged 4 commits into from
Dec 26, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 93 additions & 27 deletions config/tanzu_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ import (

// keys to Context's AdditionalMetadata map
const (
OrgIDKey = "tanzuOrgID"
ProjectNameKey = "tanzuProjectName"
SpaceNameKey = "tanzuSpaceName"
OrgIDKey = "tanzuOrgID"
ProjectNameKey = "tanzuProjectName"
SpaceNameKey = "tanzuSpaceName"
ClusterGroupNameKey = "tanzuClusterGroupName"
)

const (
Expand All @@ -39,6 +40,8 @@ type ResourceInfo struct {
ProjectName string
// SpaceName name of the Space
SpaceName string
// ClusterGroupName name of the ClusterGroup
ClusterGroupName string
}

// cmdOptions specifies the command options
Expand Down Expand Up @@ -99,30 +102,82 @@ func runCommand(commandPath string, args []string, opts *cmdOptions) (bytes.Buff
return stdout, stderr, command.Run()
}

// resourceOptions specifies the resources to use for kubeconfig generation
type resourceOptions struct {
// projectName name of the Project
projectName string
// spaceName name of the Space
spaceName string
// clusterGroupName name of the ClusterGroup
clusterGroupName string
}

type ResourceOptions func(o *resourceOptions)

func ForProject(projectName string) ResourceOptions {
return func(o *resourceOptions) {
o.projectName = strings.TrimSpace(projectName)
}
}
func ForSpace(spaceName string) ResourceOptions {
return func(o *resourceOptions) {
o.spaceName = strings.TrimSpace(spaceName)
}
}
func ForClusterGroup(clusterGroupName string) ResourceOptions {
return func(o *resourceOptions) {
o.clusterGroupName = strings.TrimSpace(clusterGroupName)
}
}
anujc25 marked this conversation as resolved.
Show resolved Hide resolved

// GetKubeconfigForContext returns the kubeconfig for any arbitrary Tanzu resource in the Tanzu object hierarchy
// referred by the Tanzu context
// Pre-reqs: project and space names should be valid
// Pre-reqs: project, space and clustergroup names should be valid
//
// Notes:
// If projectName and spaceName is empty string the kubeconfig generated would be pointing to Tanzu org
//
// ex: kubeconfig's cluster.server URL : https://endpoint/org/orgid
// Use Case 1: Get the kubeconfig pointing to Tanzu org
// -> projectName = ""
// -> spaceName = ""
// -> clusterGroupName = ""
// ex: kubeconfig's cluster.server URL : https://endpoint/org/orgid
//
// If projectName is valid projectName and spaceName is empty string the kubeconfig generated would be pointing to Tanzu project
// Use Case 2: Get the kubeconfig pointing to Tanzu project
// -> projectName = "PROJECTNAME"
// -> spaceName = ""
// -> clusterGroupName = ""
// ex: kubeconfig's cluster.server URL : https://endpoint/org/orgid/project/<projectName>
//
// ex: kubeconfig's cluster.server URL : https://endpoint/org/orgid/project/<projectName>
// Use Case 3: Get the kubeconfig pointing to Tanzu space
// -> projectName = "PROJECTNAME"
// -> spaceName = "SPACENAME"
// -> clusterGroupName = ""
// ex: kubeconfig's cluster.server URL : https://endpoint/org/orgid/project/<projectName>/space/<spaceName>
//
// similarly if both project and space names are valid names the kubeconfig generated would be pointing to Tanzu space
// Use Case 4: Get the kubeconfig pointing to Tanzu clustergroup
// -> projectName = "PROJECTNAME"
// -> spaceName = ""
// -> clusterGroupName = "CLUSTERGROUPNAME"
// ex: kubeconfig's cluster.server URL : https://endpoint/org/orgid/project/<projectName>/clustergroup/<clustergroupName>
//
// ex: kubeconfig's cluster.server URL: https://endpoint/org/orgid/project/<projectName>/space/<spaceName>
func GetKubeconfigForContext(contextName, projectName, spaceName string) ([]byte, error) {
// Note: Specifying `spaceName` and `clusterGroupName` both at the same time is incorrect input.
func GetKubeconfigForContext(contextName string, opts ...ResourceOptions) ([]byte, error) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function only supports getting kubeconfig for the tanzu context-type and no other context type.

Should we consider renaming this function to be more specific like GetKubeconfigForTanzuContext?
Or
Should we consider supporting the kubernetes context-type and make this API more generic? cc @prkalle @vuil @marckhouzam

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does renaming cause a backwards compatibility problem?

Copy link
Contributor

@prkalle prkalle Dec 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially these methods were under tae package and it was not an issue as user has to call them as tae.GetKubeconfigForContext, but later due to renaming the context type to tanzu and re-organizing them under config package, it is causing confusion. So, i feel it is good to rename to avoid confusion. I believe these changes are breaking ( I see SetTanzuContextActiveResource()signature also changed), so we can use this oppurtunity if possible (there should be only 2 affected plugins project and space).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback @prkalle and @marckhouzam.

Does renaming cause a backwards compatibility problem?

I think all the APIs related to the tanzu context are experimental APIs and we should be able to make changes to the API at the moment. Also, my existing changes do update the API signature as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will keep this thread open to get feedback from @vuil as well. However, I am not planning to do this rename as part of this PR. If we decide, I will create follow-up PR for the rename.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the idea of supporting the kubernetes context-type is interesting.
I'm not sure if it would be easy.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest keeping the name look into add suport the kubernetes context-type in a followup, since the usecase for wanting to use this API for tanzu context is not that different from when context type is kubernetes. I also think it should be quite straightfoward to add this support.

ctx, err := GetContext(contextName)
if err != nil {
return nil, err
}

rOptions := &resourceOptions{}
for _, opt := range opts {
opt(rOptions)
}

if ctx.ContextType != configtypes.ContextTypeTanzu {
return nil, errors.Errorf("context must be of type: %s", configtypes.ContextTypeTanzu)
}
if rOptions.spaceName != "" && rOptions.clusterGroupName != "" {
return nil, errors.Errorf("incorrect resource options provided. Both space and clustergroup are set but only one can be set")
}

kc, err := kubeconfig.ReadKubeConfig(ctx.ClusterOpts.Path)
if err != nil {
Expand All @@ -133,7 +188,7 @@ func GetKubeconfigForContext(contextName, projectName, spaceName string) ([]byte
if err != nil {
return nil, errors.Wrap(err, "failed to minify the kubeconfig")
}
updateKubeconfigServerURL(kc, ctx, projectName, spaceName)
updateKubeconfigServerURL(kc, ctx, rOptions)

kubeconfigBytes, err := yaml.Marshal(kc)
if err != nil {
Expand All @@ -142,36 +197,40 @@ func GetKubeconfigForContext(contextName, projectName, spaceName string) ([]byte
return kubeconfigBytes, nil
}

func prepareClusterServerURL(context *configtypes.Context, projectName, spaceName string) string {
func prepareClusterServerURL(context *configtypes.Context, rOptions *resourceOptions) string {
serverURL := context.ClusterOpts.Endpoint
if projectName == "" {
if rOptions.projectName == "" {
return serverURL
}
serverURL = serverURL + "/project/" + projectName
serverURL = serverURL + "/project/" + rOptions.projectName

if spaceName == "" {
return serverURL
if rOptions.spaceName != "" {
return serverURL + "/space/" + rOptions.spaceName
}
if rOptions.clusterGroupName != "" {
return serverURL + "/clustergroup/" + rOptions.clusterGroupName
}
return serverURL + "/space/" + spaceName
return serverURL
}

func updateKubeconfigServerURL(kc *kubeconfig.Config, cliContext *configtypes.Context, projectName, spaceName string) {
func updateKubeconfigServerURL(kc *kubeconfig.Config, cliContext *configtypes.Context, rOptions *resourceOptions) {
currentContextName := kc.CurrentContext
context := kubeconfig.GetContext(kc, currentContextName)
cluster := kubeconfig.GetCluster(kc, context.Context.Cluster)
cluster.Cluster.Server = prepareClusterServerURL(cliContext, projectName, spaceName)
cluster.Cluster.Server = prepareClusterServerURL(cliContext, rOptions)
}

// SetTanzuContextActiveResource sets the active Tanzu resource for the given context and also updates
// the kubeconfig referenced by the context of type Tanzu
//
// Pre-reqs: project and space names should be valid
// Pre-reqs: project and space/clustergroup names should be valid
//
// Note: To set
// - a space as active resource, both project and space names are required
// - a clustergroup as active resource, both project and clustergroup names are required
// - a project as active resource, only project name is required (space should be empty string)
// - org as active resource, both project and space names should be empty strings
func SetTanzuContextActiveResource(contextName, projectName, spaceName string, opts ...CommandOptions) error {
// - org as active resource, project, space and clustergroup names should be empty strings
func SetTanzuContextActiveResource(contextName string, resourceInfo ResourceInfo, opts ...CommandOptions) error {
// For now, the implementation expects env var TANZU_BIN to be set and
// pointing to the core CLI binary used to invoke setting the active Tanzu resource.

Expand All @@ -186,7 +245,13 @@ func SetTanzuContextActiveResource(contextName, projectName, spaceName string, o
}

altCommandArgs := []string{customCommandName}
args := []string{"context", "update", "tanzu-active-resource", contextName, "--project", projectName, "--space", spaceName}
args := []string{"context", "update", "tanzu-active-resource", contextName, "--project", resourceInfo.ProjectName}
if resourceInfo.SpaceName != "" {
args = append(args, "--space", resourceInfo.SpaceName)
}
if resourceInfo.ClusterGroupName != "" {
args = append(args, "--clustergroup", resourceInfo.ClusterGroupName)
}

altCommandArgs = append(altCommandArgs, args...)

Expand Down Expand Up @@ -218,9 +283,10 @@ func GetTanzuContextActiveResource(contextName string) (*ResourceInfo, error) {
return nil, errors.New("context is missing the Tanzu metadata")
}
activeResourceInfo := &ResourceInfo{
OrgID: stringValue(ctx.AdditionalMetadata[OrgIDKey]),
ProjectName: stringValue(ctx.AdditionalMetadata[ProjectNameKey]),
SpaceName: stringValue(ctx.AdditionalMetadata[SpaceNameKey]),
OrgID: stringValue(ctx.AdditionalMetadata[OrgIDKey]),
ProjectName: stringValue(ctx.AdditionalMetadata[ProjectNameKey]),
SpaceName: stringValue(ctx.AdditionalMetadata[SpaceNameKey]),
ClusterGroupName: stringValue(ctx.AdditionalMetadata[ClusterGroupNameKey]),
}
return activeResourceInfo, nil
}
Expand Down
31 changes: 24 additions & 7 deletions config/tanzu_context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ func TestGetKubeconfigForContext(t *testing.T) {
err = SetContext(c, false)
assert.NoError(t, err)

// Test getting the kubeconfig for an arbitrary Tanzu resource
kubeconfigBytes, err := GetKubeconfigForContext(c.Name, "project1", "space1")
// Test getting the kubeconfig for a space within a project
kubeconfigBytes, err := GetKubeconfigForContext(c.Name, ForProject("project1"), ForSpace("space1"))
assert.NoError(t, err)
c, err = GetContext("test-tanzu")
assert.NoError(t, err)
Expand All @@ -118,8 +118,8 @@ func TestGetKubeconfigForContext(t *testing.T) {
cluster := kubeconfig.GetCluster(&kc, "tanzu-cli-mytanzu/current")
assert.Equal(t, cluster.Cluster.Server, c.ClusterOpts.Endpoint+"/project/project1/space/space1")

// Test getting the kubeconfig for an arbitrary Tanzu resource
kubeconfigBytes, err = GetKubeconfigForContext(c.Name, "project2", "")
// Test getting the kubeconfig for a project
kubeconfigBytes, err = GetKubeconfigForContext(c.Name, ForProject("project2"))
assert.NoError(t, err)
c, err = GetContext("test-tanzu")
assert.NoError(t, err)
Expand All @@ -128,10 +128,27 @@ func TestGetKubeconfigForContext(t *testing.T) {
cluster = kubeconfig.GetCluster(&kc, "tanzu-cli-mytanzu/current")
assert.Equal(t, cluster.Cluster.Server, c.ClusterOpts.Endpoint+"/project/project2")

// Test getting the kubeconfig for a clustergroup within a project
kubeconfigBytes, err = GetKubeconfigForContext(c.Name, ForProject("project2"), ForClusterGroup("clustergroup1"))
assert.NoError(t, err)
c, err = GetContext("test-tanzu")
assert.NoError(t, err)
err = yaml.Unmarshal(kubeconfigBytes, &kc)
assert.NoError(t, err)
cluster = kubeconfig.GetCluster(&kc, "tanzu-cli-mytanzu/current")
assert.Equal(t, cluster.Cluster.Server, c.ClusterOpts.Endpoint+"/project/project2/clustergroup/clustergroup1")

// Test getting the kubeconfig with incorrect resource combination (request kubeconfig for space and clustergroup)
c, err = GetContext("test-tanzu")
assert.NoError(t, err)
_, err = GetKubeconfigForContext(c.Name, ForProject("project2"), ForSpace("space1"), ForClusterGroup("clustergroup1"))
assert.Error(t, err)
assert.ErrorContains(t, err, "incorrect resource options provided. Both space and clustergroup are set but only one can be set")

// Test getting the kubeconfig for an arbitrary Tanzu resource for non Tanzu context
nonTanzuCtx, err := GetContext("test-mc")
assert.NoError(t, err)
_, err = GetKubeconfigForContext(nonTanzuCtx.Name, "project2", "")
_, err = GetKubeconfigForContext(nonTanzuCtx.Name, ForProject("project2"))
assert.Error(t, err)
assert.ErrorContains(t, err, "context must be of type: tanzu")
}
anujc25 marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -269,7 +286,7 @@ func TestSetTanzuContextActiveResource(t *testing.T) {

// Test-1:
// - verify correct string gets printed to default stdout and stderr
err = SetTanzuContextActiveResource("test-context", "projectA", "spaceA")
err = SetTanzuContextActiveResource("test-context", ResourceInfo{ProjectName: "projectA", SpaceName: "spaceA"})
w.Close()
stdoutRecieved := <-c

Expand All @@ -284,7 +301,7 @@ func TestSetTanzuContextActiveResource(t *testing.T) {
// Test-2: when external stdout and stderr are provided with WithStdout, WithStderr options,
// verify correct string gets printed to provided custom stdout/stderr
var combinedOutputBuff bytes.Buffer
err = SetTanzuContextActiveResource("test-context", "projectA", "spaceA", WithOutputWriter(&combinedOutputBuff), WithErrorWriter(&combinedOutputBuff))
err = SetTanzuContextActiveResource("test-context", ResourceInfo{ProjectName: "projectA", SpaceName: "spaceA"}, WithOutputWriter(&combinedOutputBuff), WithErrorWriter(&combinedOutputBuff))
if spec.expectedFailure {
assert.NotNil(err)
} else {
Expand Down