Skip to content
This repository has been archived by the owner on Oct 10, 2023. It is now read-only.

Discover Plugins from all active contexts and Support Plugin Name conflicts across different Targets #3961

Merged
merged 24 commits into from
Nov 30, 2022
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
6b1e347
Use CLIPlugin CR to determine ContextType of the plugin
anujc25 Oct 31, 2022
6820453
Update catalog and plugin manager implementation to include ContextType
anujc25 Nov 10, 2022
8554c75
Add target for standalone plugins CLIPlugin resources
anujc25 Nov 18, 2022
d27fff7
Update implementations of builder plugin to include target in generat…
anujc25 Nov 18, 2022
050fcd0
Add rename to target comment
anujc25 Nov 19, 2022
6c58f54
Update docs
anujc25 Nov 20, 2022
646c683
Address Review Comments
anujc25 Nov 22, 2022
12b1365
Make k8s a special target and remove duplicated plugin from available…
anujc25 Nov 21, 2022
d8f5316
Address Review Comments Part-2
anujc25 Nov 23, 2022
b7fbdc2
Use Context based SplitView to list plugins
anujc25 Nov 23, 2022
82eb1a4
Use Target based SplitView to list contexts
anujc25 Nov 23, 2022
3462917
Address Review Comments Part-3
anujc25 Nov 28, 2022
cccd240
Use underscore as delimeter for pluginName_Target key in catalog
anujc25 Nov 28, 2022
6c93e9b
Fix plugin deletion issue
anujc25 Nov 28, 2022
22c5e12
Update CLIPlugin CRD to 'cliplugins' and 'core-management-plugins' pa…
anujc25 Nov 28, 2022
54bedbb
Address Comments from Demo
anujc25 Nov 29, 2022
de62436
Address Review Comments Part-4
anujc25 Nov 29, 2022
a866977
Fix docker-build for cliplugins
anujc25 Nov 29, 2022
2886ab4
Set current (currentServer) only if context is of type k8s
anujc25 Nov 30, 2022
f466a62
Address Review Comments Part-5
anujc25 Nov 30, 2022
99151fe
Address Review Comments Part-6
anujc25 Nov 30, 2022
4cabafc
Fix unit test
anujc25 Nov 30, 2022
fea415e
Make target field optional in CLIPlugin API
anujc25 Nov 30, 2022
992f974
Do not lock when reading currentContextMap
anujc25 Nov 30, 2022
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
4 changes: 0 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,6 @@ ifneq ($(strip $(TANZU_CORE_BUCKET)),) # Name of the core plugin repository buck
LD_FLAGS += -X 'github.com/vmware-tanzu/tanzu-framework/cli/core/pkg/config.CoreBucketName=$(TANZU_CORE_BUCKET)'
endif

ifeq ($(TANZU_FORCE_NO_INIT), true) # Force No installation of plugins and override TANZU_NO_INIT env variable
LD_FLAGS += -X 'github.com/vmware-tanzu/tanzu-framework/cli/core/pkg/command.forceNoInit=true'
endif

ifneq ($(strip $(TKG_DEFAULT_IMAGE_REPOSITORY)),)
LD_FLAGS += -X 'github.com/vmware-tanzu/tanzu-framework/cli/core/pkg/config.DefaultStandaloneDiscoveryRepository=$(TKG_DEFAULT_IMAGE_REPOSITORY)'
endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,16 @@ spec:
The value should be a valid semantic version as defined in https://semver.org/.
E.g., 2.0.1
type: string
target:
description: Target specifies the target of the plugin. Only needed
for standalone plugins
type: string
required:
- artifacts
- description
- optional
- recommendedVersion
- target
type: object
required:
- metadata
Expand Down
16 changes: 16 additions & 0 deletions apis/cli/v1alpha1/cliplugin_helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright 2022 VMware, Inc. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package v1alpha1

// StringToTarget converts string to Target type
func StringToTarget(target string) Target {
if target == string(targetK8s) || target == string(TargetK8s) {
return TargetK8s
} else if target == string(targetTMC) || target == string(TargetTMC) {
return TargetTMC
} else if target == string(TargetNone) {
return TargetNone
}
return TargetNone
}
18 changes: 18 additions & 0 deletions apis/cli/v1alpha1/cliplugin_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// Target is the namespace of the CLI to which plugin is applicable
type Target string

const (
// TargetK8s is a kubernetes target of the CLI
TargetK8s Target = "kubernetes"
targetK8s Target = "k8s"

// TargetTMC is a Tanzu Mission Control target of the CLI
TargetTMC Target = "mission-control"
targetTMC Target = "tmc"

// TargetNone is used for plugins that are not associated with any target
TargetNone Target = ""
)

// ArtifactList contains an Artifact object for every supported platform of a version.
type ArtifactList []Artifact

Expand Down Expand Up @@ -42,6 +58,8 @@ type CLIPluginSpec struct {
// To view the list of plugin, user can use `tanzu plugin list` and
// to download a specific plugin run, `tanzu plugin install <plugin-name>`
Optional bool `json:"optional"`
// Target specifies the target of the plugin. Only needed for standalone plugins
Target Target `json:"target"`
}

//+kubebuilder:object:root=true
Expand Down
4 changes: 0 additions & 4 deletions cli/core/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,6 @@ ifneq ($(strip $(TANZU_CORE_BUCKET)),)
LD_FLAGS += -X 'github.com/vmware-tanzu/tanzu-framework/cli/core/pkg/config.CoreBucketName=$(TANZU_CORE_BUCKET)'
endif

ifeq ($(TANZU_FORCE_NO_INIT), true)
LD_FLAGS += -X 'github.com/vmware-tanzu/tanzu-framework/cli/core/pkg/command.forceNoInit=true'
endif

anujc25 marked this conversation as resolved.
Show resolved Hide resolved
ifneq ($(strip $(TKG_DEFAULT_IMAGE_REPOSITORY)),)
LD_FLAGS += -X 'github.com/vmware-tanzu/tanzu-framework/cli/core/pkg/config.DefaultStandaloneDiscoveryRepository=$(TKG_DEFAULT_IMAGE_REPOSITORY)'
endif
Expand Down
241 changes: 94 additions & 147 deletions cli/core/docs/cli/cli-architecture.md

Large diffs are not rendered by default.

188 changes: 107 additions & 81 deletions cli/core/docs/design/context-aware-plugin-discovery-design.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion cli/core/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ require (
github.com/aunum/log v0.0.0-20200821225356-38d2e2c8b489
github.com/briandowns/spinner v1.19.0
github.com/cppforlife/go-cli-ui v0.0.0-20200716203538-1e47f820817f
github.com/fatih/color v1.13.0
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/google/gnostic v0.5.7-v3refs
github.com/google/go-containerregistry v0.7.0
Expand Down Expand Up @@ -73,7 +74,6 @@ require (
github.com/docker/docker-credential-helpers v0.6.4 // indirect
github.com/emicklei/go-restful v2.15.0+incompatible // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-logr/logr v1.2.2 // indirect
Expand Down
37 changes: 16 additions & 21 deletions cli/core/pkg/catalog/catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@ package catalog

import (
"bytes"
"fmt"
"os"
"path/filepath"

cliv1alpha1 "github.com/vmware-tanzu/tanzu-framework/apis/cli/v1alpha1"

"github.com/pkg/errors"
apimachineryjson "k8s.io/apimachinery/pkg/runtime/serializer/json"

"github.com/vmware-tanzu/tanzu-framework/cli/core/pkg/common"
"github.com/vmware-tanzu/tanzu-framework/cli/core/pkg/utils"
cliapi "github.com/vmware-tanzu/tanzu-framework/cli/runtime/apis/cli/v1alpha1"
configapi "github.com/vmware-tanzu/tanzu-framework/cli/runtime/apis/config/v1alpha1"
)

const (
Expand Down Expand Up @@ -61,11 +63,13 @@ func NewContextCatalog(context string) (*ContextCatalog, error) {

// Upsert inserts/updates the given plugin.
func (c *ContextCatalog) Upsert(plugin *cliapi.PluginDescriptor) error {
c.plugins[plugin.Name] = plugin.InstallationPath
pluginNameTarget := PluginNameTarget(plugin.Name, plugin.Target)

c.plugins[pluginNameTarget] = plugin.InstallationPath
c.sharedCatalog.IndexByPath[plugin.InstallationPath] = *plugin

if !utils.ContainsString(c.sharedCatalog.IndexByName[plugin.Name], plugin.InstallationPath) {
c.sharedCatalog.IndexByName[plugin.Name] = append(c.sharedCatalog.IndexByName[plugin.Name], plugin.InstallationPath)
if !utils.ContainsString(c.sharedCatalog.IndexByName[pluginNameTarget], plugin.InstallationPath) {
c.sharedCatalog.IndexByName[pluginNameTarget] = append(c.sharedCatalog.IndexByName[pluginNameTarget], plugin.InstallationPath)
}

return saveCatalogCache(c.sharedCatalog)
Expand Down Expand Up @@ -121,11 +125,7 @@ func newSharedCatalog() (*cliapi.Catalog, error) {
IndexByPath: map[string]cliapi.PluginDescriptor{},
IndexByName: map[string][]string{},
StandAlonePlugins: map[string]string{},
StandAlonePluginsByContextType: map[configapi.ContextType]cliapi.PluginAssociation{
configapi.CtxTypeK8s: map[string]string{},
configapi.CtxTypeTMC: map[string]string{},
},
ServerPlugins: map[string]cliapi.PluginAssociation{},
ServerPlugins: map[string]cliapi.PluginAssociation{},
}

err := ensureRoot()
Expand Down Expand Up @@ -166,15 +166,6 @@ func getCatalogCache() (catalog *cliapi.Catalog, err error) {
if c.StandAlonePlugins == nil {
c.StandAlonePlugins = map[string]string{}
}
if c.StandAlonePluginsByContextType == nil {
c.StandAlonePluginsByContextType = map[configapi.ContextType]cliapi.PluginAssociation{}
}
if _, ok := c.StandAlonePluginsByContextType[configapi.CtxTypeK8s]; !ok {
c.StandAlonePluginsByContextType[configapi.CtxTypeK8s] = map[string]string{}
}
if _, ok := c.StandAlonePluginsByContextType[configapi.CtxTypeTMC]; !ok {
c.StandAlonePluginsByContextType[configapi.CtxTypeTMC] = map[string]string{}
}
if c.ServerPlugins == nil {
c.ServerPlugins = map[string]cliapi.PluginAssociation{}
}
Expand All @@ -184,9 +175,6 @@ func getCatalogCache() (catalog *cliapi.Catalog, err error) {

// saveCatalogCache saves the catalog in the local directory.
func saveCatalogCache(catalog *cliapi.Catalog) error {
// Using the K8s context type since it is the only one available publicly.
catalog.StandAlonePluginsByContextType[configapi.CtxTypeK8s] = catalog.StandAlonePlugins

catalogCachePath := getCatalogCachePath()
_, err := os.Stat(catalogCachePath)
if os.IsNotExist(err) {
Expand Down Expand Up @@ -254,3 +242,10 @@ func UpdateCatalogCache() error {

return saveCatalogCache(c)
}

func PluginNameTarget(pluginName string, target cliv1alpha1.Target) string {
if target == "" {
return pluginName
}
return fmt.Sprintf("%s_%s", pluginName, target)
}
1 change: 1 addition & 0 deletions cli/core/pkg/cli/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func GetCmd(p *cliapi.PluginDescriptor) *cobra.Command {
DisableFlagParsing: true,
Annotations: map[string]string{
"group": string(p.Group),
"scope": p.Scope,
},
Hidden: p.Hidden,
Aliases: p.Aliases,
Expand Down
8 changes: 1 addition & 7 deletions cli/core/pkg/command/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,13 +218,7 @@ var initConfigCmd = &cobra.Command{
cfg.ClientOptions.CLI = &configapi.CLIOptions{}
}

serverName := ""
server, err := cfg.GetCurrentServer()
if err == nil && server != nil {
serverName = server.Name
}

serverPluginDescriptors, standalonePluginDescriptors, err := pluginmanager.InstalledPlugins(serverName)
serverPluginDescriptors, standalonePluginDescriptors, err := pluginmanager.InstalledPlugins()
if err != nil {
return err
}
Expand Down
91 changes: 68 additions & 23 deletions cli/core/pkg/command/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"crypto/tls"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
Expand All @@ -15,6 +16,7 @@ import (
"time"

"github.com/aunum/log"
"github.com/fatih/color"
"github.com/spf13/cobra"
"golang.org/x/oauth2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -125,7 +127,7 @@ func createCtx(_ *cobra.Command, _ []string) (err error) {

// Sync all required plugins if the "features.global.context-aware-cli-for-plugins" feature is enabled
if config.IsFeatureActivated(cliconfig.FeatureContextAwareCLIForPlugins) {
if err = pluginmanager.SyncPlugins(ctx.Name); err != nil {
if err = pluginmanager.SyncPlugins(); err != nil {
log.Warning("unable to automatically sync the plugins from target context. Please run 'tanzu plugin sync' command to sync plugins manually")
}
}
Expand Down Expand Up @@ -475,29 +477,12 @@ func listCtx(cmd *cobra.Command, _ []string) error {
return err
}

op := component.NewOutputWriter(cmd.OutOrStdout(), outputFormat, "Name", "Type", "IsManagementCluster", "IsCurrent", "Endpoint", "KubeConfigPath", "KubeContext")
for _, ctx := range cfg.KnownContexts {
if ctxType != "" && ctx.Type != configapi.ContextType(ctxType) {
continue
}
isMgmtCluster := ctx.IsManagementCluster()
isCurrent := ctx.Name == cfg.CurrentContext[ctx.Type]
if onlyCurrent && !isCurrent {
continue
}

var endpoint, path, context string
switch ctx.Type {
case configapi.CtxTypeTMC:
endpoint = ctx.GlobalOpts.Endpoint
default:
endpoint = ctx.ClusterOpts.Endpoint
path = ctx.ClusterOpts.Path
context = ctx.ClusterOpts.Context
}
op.AddRow(ctx.Name, ctx.Type, isMgmtCluster, isCurrent, endpoint, path, context)
if outputFormat == "" || outputFormat == string(component.TableOutputType) {
displayContextListOutputSplitViewTarget(cfg, cmd.OutOrStdout())
} else {
displayContextListOutputListView(cfg, cmd.OutOrStdout())
}
op.Render()

return nil
}

Expand Down Expand Up @@ -642,3 +627,63 @@ func useCtx(_ *cobra.Command, args []string) error {
}
return nil
}

func displayContextListOutputListView(cfg *configapi.ClientConfig, writer io.Writer) {
op := component.NewOutputWriter(writer, outputFormat, "Name", "Type", "IsManagementCluster", "IsCurrent", "Endpoint", "KubeConfigPath", "KubeContext")
for _, ctx := range cfg.KnownContexts {
if ctxType != "" && ctx.Type != configapi.ContextType(ctxType) {
continue
}
isMgmtCluster := ctx.IsManagementCluster()
isCurrent := ctx.Name == cfg.CurrentContext[ctx.Type]
if onlyCurrent && !isCurrent {
continue
}

var endpoint, path, context string
switch ctx.Type {
case configapi.CtxTypeTMC:
endpoint = ctx.GlobalOpts.Endpoint
default:
endpoint = ctx.ClusterOpts.Endpoint
path = ctx.ClusterOpts.Path
context = ctx.ClusterOpts.Context
}
op.AddRow(ctx.Name, ctx.Type, isMgmtCluster, isCurrent, endpoint, path, context)
}
op.Render()
}

func displayContextListOutputSplitViewTarget(cfg *configapi.ClientConfig, writer io.Writer) {
outputWriterK8sTarget := component.NewOutputWriter(writer, outputFormat, "Name", "IsActive", "Endpoint", "KubeConfigPath", "KubeContext")
outputWriterTMCTarget := component.NewOutputWriter(writer, outputFormat, "Name", "IsActive", "Endpoint")
for _, ctx := range cfg.KnownContexts {
if ctxType != "" && ctx.Type != configapi.ContextType(ctxType) {
continue
}
isCurrent := ctx.Name == cfg.CurrentContext[ctx.Type]
if onlyCurrent && !isCurrent {
continue
}

var endpoint, path, context string
switch ctx.Type {
case configapi.CtxTypeTMC:
endpoint = ctx.GlobalOpts.Endpoint
outputWriterTMCTarget.AddRow(ctx.Name, isCurrent, endpoint)
default:
endpoint = ctx.ClusterOpts.Endpoint
path = ctx.ClusterOpts.Path
context = ctx.ClusterOpts.Context
outputWriterK8sTarget.AddRow(ctx.Name, isCurrent, endpoint, path, context)
}
}

cyanBold := color.New(color.FgCyan).Add(color.Bold)
cyanBoldItalic := color.New(color.FgCyan).Add(color.Bold, color.Italic)
_, _ = cyanBold.Println("Target: ", cyanBoldItalic.Sprintf("%s", configapi.CtxTypeK8s))
outputWriterK8sTarget.Render()

_, _ = cyanBold.Println("Target: ", cyanBoldItalic.Sprintf("%s", configapi.CtxTypeTMC))
outputWriterTMCTarget.Render()
}
25 changes: 16 additions & 9 deletions cli/core/pkg/command/discovery_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"fmt"
"strings"

"github.com/vmware-tanzu/tanzu-framework/cli/core/pkg/config"

"github.com/aunum/log"

"github.com/pkg/errors"
Expand Down Expand Up @@ -69,17 +71,22 @@ var listDiscoverySourceCmd = &cobra.Command{
outputFromDiscoverySources(cfg.ClientOptions.CLI.DiscoverySources, common.PluginScopeStandalone, output)
}

// Get context scoped discoveries
server, err := configlib.GetCurrentServer()
if err == nil && server != nil {
var serverDiscoverySources []configapi.PluginDiscovery
if server.DiscoverySources == nil {
serverDiscoverySources = configlib.GetDiscoverySources(server.Name)
} else {
serverDiscoverySources = server.DiscoverySources
// If context-target feature is activated, get discovery sources from all active context
// else get discovery sources from current server
if configlib.IsFeatureActivated(config.FeatureContextCommand) {
mapContexts, err := configlib.GetAllCurrentContextsMap()
if err == nil {
for _, context := range mapContexts {
marckhouzam marked this conversation as resolved.
Show resolved Hide resolved
outputFromDiscoverySources(context.DiscoverySources, common.PluginScopeContext, output)
}
}
} else {
server, err := configlib.GetCurrentServer()
if err == nil && server != nil {
outputFromDiscoverySources(server.DiscoverySources, common.PluginScopeContext, output)
}
outputFromDiscoverySources(serverDiscoverySources, common.PluginScopeContext, output)
}

output.Render()

return nil
Expand Down
Loading