Skip to content

Commit

Permalink
Update the tanzu login command to log in to TAP SaaS
Browse files Browse the repository at this point in the history
- Refactor/update the existing deprecated tanzu login command to login to TAP SaaS

Signed-off-by: Prem Kumar Kalle <[email protected]>

Update the docs for tanzu login command

Signed-off-by: Prem Kumar Kalle <[email protected]>
  • Loading branch information
prkalle committed Apr 10, 2024
1 parent 55f7ce5 commit 0f1da9c
Show file tree
Hide file tree
Showing 6 changed files with 298 additions and 577 deletions.
96 changes: 80 additions & 16 deletions docs/quickstart/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,72 @@ tanzu plugin upgrade <plugin> [--target <target>]
This command will update the specified plugin to the recommendedVersion
associated with this plugin's entry found in the plugin repository.

### Logging into Tanzu Application Platform

To log in to the Tanzu Application Platform and create a context of type tanzu, use the following command:

```console
tanzu login
```

This command will log in to the Tanzu Application Platform and creates a context associated
with the organization's name that you are logging in to. Once the context is created,
you can manage it using the `tanzu context` command.

After logging in, you can use the tanzu login command again to update the authentication aspects
of the existing context, while keeping the rest of the context data intact.

#### Tanzu login

User can log in using interactive login (default mechanism) or by utilizing an API Token.

##### Interactive Login

By default, CLI uses interactive login. The CLI opens the browser for the user to log in.
CLI will attempt to log in interactively to the user's default Cloud Services organization. You can override or choose a
custom organization by setting the `TANZU_CLI_CLOUD_SERVICES_ORGANIZATION_ID` environment variable with the custom
organization ID value. More information regarding organizations in Cloud Services and how to obtain the organization ID
can be
found [here](https://docs.vmware.com/en/VMware-Cloud-services/services/Using-VMware-Cloud-Services/GUID-CF9E9318-B811-48CF-8499-9419997DC1F8.html).

After successful authentication, a context of type "tanzu" is created.

Example command for interactive login:

```console
tanzu login
```

Notes:

- For terminal hosts without a browser, users can set the `TANZU_CLI_OAUTH_LOCAL_LISTENER_PORT` environment variable
with a chosen port number. Then, run the `tanzu login` command.
The CLI will show an OAuth URL link in the console and start a local listener on the specified port.
Users can use SSH port forwarding to forward the port on their machine to the terminal/server machine where the
local listener is running. Once the port forwarding is initiated, the user can open the OAuth URL in their local
machine browser to complete the login and create a Tanzu context.

- Alternatively, users can run the `tanzu login` command in the
terminal host. The CLI will display the OAuth URL link in the console and provide an option for the user to paste
the Auth code from the browser URL([Image reference](./images/interactive_login_copy_authcode.png)) to the
console.

##### API Token

Example command to log in using an API token:

```console
TANZU_API_TOKEN=<APIToken> tanzu login
```

Users can persist the environment variable in the CLI configuration file, which will be used for each CLI command
invocation:

```console
tanzu config set env.TANZU_API_TOKEN <api_token>
tanzu login
```

### Creating and connecting to a new context

```console
Expand Down Expand Up @@ -288,26 +354,24 @@ updated.

Below is the summary of the changes to expect:

### tanzu login
### Deprecated tanzu login Command

The `tanzu login` command has undergone significant changes.

Two important changes apply to the `tanzu login` command.
- **Formerly a Plugin:** Initially, this command was provided by the login plugin
and would appear in the list generated by `tanzu plugin list`. However, it has
now been transitioned to a core CLI command.

First, this command used to be provided by the `login` plugin. The
`tanzu plugin list` command would therefore show the `login` plugin, if it was
installed. This is no longer the case as the `tanzu login` command is now a
core CLI command. The `tanzu plugin list` command will therefore no longer
show the `login` plugin. An exception to the last statement is if the `login`
plugin was previously installed through a legacy version of the CLI.
- **Deprecation**: The core `tanzu login` command was deprecated in favor of
the `tanzu context create` or `tanzu context use` commands for managing contexts.

If the user needs to continue using any legacy Tanzu CLI for a while,
it is preferable to keep the `login` plugin installed, as it is needed
for such CLIs. Once the user no longer needs any legacy CLI, they
can run `tanzu plugin uninstall login` to remove this plugin for the new CLI.
- **Repurposing:** The deprecated `tanzu login` core command has been repurposed
to facilitate logging into the Tanzu Application Platform. It is no longer
considered deprecated.

The second change to the `tanzu login` command is that it is now deprecated
and therefore no longer shown in the help text when running `tanzu -h`.
The `tanzu context create` or `tanzu context use` commands should be used
instead of the deprecated `tanzu login` command.
- **For Legacy CLI Users:** If you still require the use of any legacy Tanzu CLI,
it is advisable to retain the `login plugin`. Once you no longer need the legacy CLI,
you can uninstall the plugin using `tanzu plugin uninstall login`.

### tanzu config server

Expand Down
2 changes: 1 addition & 1 deletion pkg/command/ceip_participation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ var _ = Describe("ceip-participation command tests", func() {
os.Unsetenv("TANZU_CONFIG_NEXT_GEN")
os.RemoveAll(tkgConfigFile.Name())
os.RemoveAll(tkgConfigFileNG.Name())
resetLoginCommandFlags()
resetContextCommandFlags()
})
Context("ceip-participation set to true", func() {
It("ceip-participation set should be successful and get should return status as 'Opt-in' status", func() {
Expand Down
128 changes: 89 additions & 39 deletions pkg/command/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ func createCtx(cmd *cobra.Command, args []string) (err error) {
err = k8sLogin(ctx)
} else if ctx.ContextType == configtypes.ContextTypeTanzu {
// Tanzu control plane login
err = globalTanzuLogin(ctx)
err = globalTanzuLogin(ctx, nil)
} else {
err = globalLogin(ctx)
}
Expand All @@ -281,6 +281,8 @@ func createCtx(cmd *cobra.Command, args []string) (err error) {

// syncContextPlugins syncs the plugins for the given context type
// if listPlugins is true, it will list the plugins that will be installed for the given context type
//
//nolint:unparam
func syncContextPlugins(cmd *cobra.Command, contextType configtypes.ContextType, ctxName string, listPlugins bool) error {
plugins, err := pluginmanager.DiscoverPluginsForContextType(contextType)
errList := make([]error, 0)
Expand Down Expand Up @@ -617,70 +619,111 @@ func globalLogin(c *configtypes.Context) (err error) {
}

// format
fmt.Println()
fmt.Fprintln(os.Stderr)
log.Success("successfully created a TMC context")
return nil
}

func globalTanzuLogin(c *configtypes.Context) error {
var claims *csp.Claims
var err error
apiTokenValue, apiTokenExists := os.LookupEnv(config.EnvAPITokenKey)
// Use API Token login flow if TANZU_API_TOKEN environment variable is set, else fall back to default interactive login flow
if apiTokenExists {
log.Info("API token env var is set")
claims, err = doCSPAPITokenAuthAndUpdateContext(c, apiTokenValue)
} else {
claims, err = doCSPInteractiveLoginAndUpdateContext(c)
}
func globalTanzuLogin(c *configtypes.Context, generateContextNameFunc func(orgName, endpoint string, isStaging bool) string) error {
claims, err := doCSPAuthentication(c)
if err != nil {
return err
}
c.AdditionalMetadata[config.OrgIDKey] = claims.OrgID

kubeCfg, kubeCtx, serverEndpoint, err := tanzuauth.GetTanzuKubeconfig(c, endpoint, claims.OrgID, endpointCACertPath, skipTLSVerify)
orgName, err := getCSPOrganizationName(c, claims)
if err != nil {
return err
}
// update the context name using the context name generator
if generateContextNameFunc != nil {
c.Name = generateContextNameFunc(orgName, endpoint, staging)
}

c.ClusterOpts.Path = kubeCfg
c.ClusterOpts.Context = kubeCtx
c.ClusterOpts.Endpoint = serverEndpoint
// update the context metadata
if err := updateTanzuContextMetadata(c, claims.OrgID, orgName); err != nil {
return err
}

err = config.AddContext(c, true)
if err != nil {
// Fetch the tanzu kubeconfig and update context
if err := updateContextWithTanzuKubeconfig(c, endpoint, claims.OrgID, endpointCACertPath, skipTLSVerify); err != nil {
return err
}

// Add the context to configuration
if err := config.AddContext(c, true); err != nil {
return err
}

// update the current context in the kubeconfig file after creating the context
err = syncCurrentKubeContext(c)
if err != nil {
if err := syncCurrentKubeContext(c); err != nil {
return errors.Wrap(err, "unable to update current kube context")
}

// format
fmt.Println()
orgName := getCSPOrgName(c, claims)
// If the orgName fetching API fails(corner case), we only print the tanzu context creation success message
msg := "Successfully created a tanzu context"
if orgName != "" {
msg = fmt.Sprintf("Successfully logged into '%s' organization and created a tanzu context", orgName)
fmt.Fprintln(os.Stderr)
log.Successf("Successfully logged into '%s' organization and created a tanzu context", orgName)
return nil
}

// updateTanzuContextMetadata updates the context additional metadata
func updateTanzuContextMetadata(c *configtypes.Context, orgID, orgName string) error {
exists, err := config.ContextExists(c.Name)
if err != nil {
return err
}
log.Success(msg)
if !exists {
c.AdditionalMetadata[config.OrgIDKey] = orgID
c.AdditionalMetadata[config.OrgNameKey] = orgName
return nil
}
// This is possible only for contexts created using "tanzu login" command because
// "tanzu context create" command doesn't allow user to create duplicate contexts
existingContext, err := config.GetContext(c.Name)
if err != nil {
return err
}
// If the context exists with the same name, honor the users current context additional metadata
// which includes the org/project/space details.
c.AdditionalMetadata = existingContext.AdditionalMetadata

return nil
}

// getCSPOrgName returns the CSP Org name using the orgID from the claims.
// getCSPOrganizationName returns the CSP Org name using the orgID from the claims.
// It will return empty string if API fails
func getCSPOrgName(c *configtypes.Context, claims *csp.Claims) string {
func getCSPOrganizationName(c *configtypes.Context, claims *csp.Claims) (string, error) {
issuer := csp.ProdIssuer
if staging {
issuer = csp.StgIssuer
}
orgName, err := csp.GetOrgNameFromOrgID(claims.OrgID, c.GlobalOpts.Auth.AccessToken, issuer)
if err != nil {
return ""
return "", err
}
return orgName
return orgName, nil
}

func updateContextWithTanzuKubeconfig(c *configtypes.Context, ep, orgID, epCACertPath string, skipTLSVerify bool) error {
kubeCfg, kubeCtx, orgEndpoint, err := tanzuauth.GetTanzuKubeconfig(c, ep, orgID, epCACertPath, skipTLSVerify)
if err != nil {
return err
}
c.ClusterOpts.Path = kubeCfg
c.ClusterOpts.Context = kubeCtx
// for "tanzu" context ClusterOpts.Endpoint would always be pointing to UCP organization endpoint
c.ClusterOpts.Endpoint = orgEndpoint

return nil
}

func doCSPAuthentication(c *configtypes.Context) (*csp.Claims, error) {
apiTokenValue, apiTokenExists := os.LookupEnv(config.EnvAPITokenKey)
// Use API Token login flow if TANZU_API_TOKEN environment variable is set, else fall back to default interactive login flow
if apiTokenExists {
log.Info("API token env var is set")
return doCSPAPITokenAuthAndUpdateContext(c, apiTokenValue)
}
return doCSPInteractiveLoginAndUpdateContext(c)
}

func doCSPInteractiveLoginAndUpdateContext(c *configtypes.Context) (claims *csp.Claims, err error) {
Expand Down Expand Up @@ -1567,12 +1610,7 @@ func setTanzuCtxActiveResource(_ *cobra.Command, args []string) error {
if err != nil {
return errors.Wrap(err, "failed updating the context %q with the active tanzu resource")
}
// TODO (prkalle): Adding this fallback logic to support the backward compatibility. This should be updated to use projectID for official release
// If the projectIDStr is set it would be used for kubeconfig generation, else use the project name
projectVal := projectStr
if projectIDStr != "" {
projectVal = projectIDStr
}
projectVal := getProjectValueForKubeconfig(projectStr, projectIDStr)
err = updateTanzuContextKubeconfig(ctx, projectVal, spaceStr, clustergroupStr)
if err != nil {
return errors.Wrap(err, "failed to update the tanzu context kubeconfig")
Expand All @@ -1581,6 +1619,18 @@ func setTanzuCtxActiveResource(_ *cobra.Command, args []string) error {
return nil
}

// getProjectValueForKubeconfig return the project value to be used for UCP kubeconfig generation.
//
// Note: This method can be removed for official release as projectID would be used for kubeconfig generation.
func getProjectValueForKubeconfig(projectName, projectID string) string {
// TODO (prkalle): Adding this fallback logic to support the backward compatibility. This should be updated to use projectID for official release
// If the projectIDStr is set it would be used for kubeconfig generation, else use the project name
if projectID != "" {
return projectID
}
return projectName
}

func validateActiveResourceOptions() error {
if spaceStr != "" && clustergroupStr != "" {
return errors.Errorf("either space or clustergroup can be set as active resource. Please provide either --space or --clustergroup option")
Expand Down
1 change: 1 addition & 0 deletions pkg/command/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,7 @@ clusterOpts:
})

Describe("tanzu context unset", func() {
var name string
cmd := &cobra.Command{}
BeforeEach(func() {
targetStr = ""
Expand Down
Loading

0 comments on commit 0f1da9c

Please sign in to comment.