diff --git a/.github/workflows/plugin_tests.yaml b/.github/workflows/plugin_tests.yaml index e4e1d1647d3..cfe41eb4e74 100644 --- a/.github/workflows/plugin_tests.yaml +++ b/.github/workflows/plugin_tests.yaml @@ -82,11 +82,6 @@ jobs: echo "##[set-output name=bompath;]$(echo "$BOMCOMMENT" | awk -F : '{print $2}')" id: extract_bom - # Using `ENABLE_CONTEXT_AWARE_PLUGIN_DISCOVERY=false` to build and install cli and plugins because - # currently, context-aware-plugin-discovery does not support test plugins - # Issue: https://github.com/vmware-tanzu/tanzu-framework/issues/1164 - # above issue need to be addressed and as part of the change we can use context-aware way of plugin - # tests as part of this test workflow. - name: Build run: | if [[ ! -z "${{ steps.extract_bom.outputs.bompath }}" ]]; then @@ -94,17 +89,20 @@ jobs: fi env | sort make configure-bom - make build-cli-local ENABLE_CONTEXT_AWARE_PLUGIN_DISCOVERY=false + make build-cli-local + + - name: Cleanup + run: rm -rf ~/.tanzu ~/.tkg ~/.config ~/.cache/tanzu; docker kill $(docker ps -q) | true; docker system prune -a --volumes -f - name: Install Tanzu CLI run: | - make install-cli ENABLE_CONTEXT_AWARE_PLUGIN_DISCOVERY=false + make install-cli + tanzu config set features.global.context-aware-cli-for-plugins true - name: Install CLI plugins - run: make install-cli-plugins ENABLE_CONTEXT_AWARE_PLUGIN_DISCOVERY=false - - - name: Cleanup - run: rm -rf ~/.tanzu ~/.tkg ~/.config ~/.cache/tanzu; docker kill $(docker ps -q) | true; docker system prune -a --volumes -f + run: | + tanzu plugin install all --local artifacts/linux/amd64/cli/ + tanzu plugin install all --local artifacts-admin/linux/amd64/cli/ - name: Run cluster test plugin run: tanzu test plugin cluster diff --git a/cli/core/pkg/artifact/http.go b/cli/core/pkg/artifact/http.go index b6dffd0df59..5fc134cd6c1 100644 --- a/cli/core/pkg/artifact/http.go +++ b/cli/core/pkg/artifact/http.go @@ -9,6 +9,8 @@ import ( "io" "net/http" "time" + + "github.com/pkg/errors" ) const ( @@ -72,3 +74,8 @@ func (g *HTTPArtifact) Fetch() ([]byte, error) { return out, nil } + +// FetchTest returns test artifact +func (g *HTTPArtifact) FetchTest() ([]byte, error) { + return nil, errors.New("fetching test plugin from HTTP source is not yet supported") +} diff --git a/cli/core/pkg/artifact/interface.go b/cli/core/pkg/artifact/interface.go index fea16c71fbb..79575e14bee 100644 --- a/cli/core/pkg/artifact/interface.go +++ b/cli/core/pkg/artifact/interface.go @@ -14,6 +14,8 @@ import ( type Artifact interface { // Fetch the binary for a plugin version. Fetch() ([]byte, error) + // FetchTest the test binary for a plugin version. + FetchTest() ([]byte, error) } // NewURIArtifact creates new artifacts based on the URI diff --git a/cli/core/pkg/artifact/local.go b/cli/core/pkg/artifact/local.go index 32f13e49592..7d61a1f4362 100644 --- a/cli/core/pkg/artifact/local.go +++ b/cli/core/pkg/artifact/local.go @@ -45,3 +45,32 @@ func (l *LocalArtifact) Fetch() ([]byte, error) { } return b, nil } + +// FetchTest reads test plugin artifact based on the local plugin artifact path +// To fetch the test binary from the plugin we are using plugin binary path and creating test plugin path from it +// If the plugin binary path is `artifacts/darwin/amd64/cli/cluster/v0.27.0-dev/tanzu-cluster-darwin_amd64` +// then test plugin binary will be under `artifacts/darwin/amd64/cli/cluster/v0.27.0-dev/test/` directory +func (l *LocalArtifact) FetchTest() ([]byte, error) { + // test plugin directory based on the plugin binary location + testPluginDir := filepath.Join(filepath.Dir(l.Path), "test") + dirEntries, err := os.ReadDir(testPluginDir) + if err != nil { + return nil, errors.Wrap(err, "unable to read test plugin directory") + } + if len(dirEntries) != 1 { + return nil, errors.Errorf("Expected only 1 file under the '%v' directory, but found %v files", testPluginDir, len(dirEntries)) + } + + // Assuming the file under the test directory is the test plugin binary + testPluginFile := dirEntries[0] + if testPluginFile.IsDir() { + return nil, errors.Errorf("Expected to find test plugin binary but found directory '%v'", testPluginFile) + } + + testPluginPath := filepath.Join(testPluginDir, testPluginFile.Name()) + b, err := os.ReadFile(testPluginPath) + if err != nil { + return nil, errors.Wrapf(err, "error while reading test artifact") + } + return b, nil +} diff --git a/cli/core/pkg/artifact/local_test.go b/cli/core/pkg/artifact/local_test.go new file mode 100644 index 00000000000..e7342aaa2ec --- /dev/null +++ b/cli/core/pkg/artifact/local_test.go @@ -0,0 +1,48 @@ +// Copyright 2021 VMware, Inc. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package artifact + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +// When local artifact directory exists and test directory exists +func TestLocalArtifactWhenArtifactAndTestDirExists(t *testing.T) { + assert := assert.New(t) + + testPluginPath, err := filepath.Abs(filepath.Join("test", "local", "v0.0.1", "tanzu-plugin-darwin_amd64")) + assert.NoError(err) + artifact := NewLocalArtifact(testPluginPath) + + b, err := artifact.Fetch() + assert.NoError(err) + assert.Contains(string(b), "plugin binary") + + b, err = artifact.FetchTest() + assert.NoError(err) + assert.Contains(string(b), "test plugin binary") +} + +// When local artifact doesn't exists and multiple files exists within test directory +func TestLocalArtifactWhenArtifactDoesntExistsAndMultipleFilesUnderTest(t *testing.T) { + assert := assert.New(t) + + testPluginPath, err := filepath.Abs(filepath.Join("test", "local", "v0.0.2", "plugin_binary_does_not_exists")) + assert.NoError(err) + + artifact := NewLocalArtifact(testPluginPath) + + // When local artifact doesn't exists + _, err = artifact.Fetch() + assert.Error(err) + assert.ErrorContains(err, "error while reading artifact") + + // When multiple files exists under test directory + _, err = artifact.FetchTest() + assert.Error(err) + assert.ErrorContains(err, "Expected only 1 file under the") +} diff --git a/cli/core/pkg/artifact/oci.go b/cli/core/pkg/artifact/oci.go index 82676662a3f..b4c08595b2d 100644 --- a/cli/core/pkg/artifact/oci.go +++ b/cli/core/pkg/artifact/oci.go @@ -57,3 +57,8 @@ func (g *OCIArtifact) Fetch() ([]byte, error) { return bytesData, nil } + +// FetchTest returns test artifact +func (g *OCIArtifact) FetchTest() ([]byte, error) { + return nil, errors.New("fetching test plugin from OCI source is not yet supported") +} diff --git a/cli/core/pkg/artifact/test/local/v0.0.1/tanzu-plugin-darwin_amd64 b/cli/core/pkg/artifact/test/local/v0.0.1/tanzu-plugin-darwin_amd64 new file mode 100644 index 00000000000..b266d35e9e4 --- /dev/null +++ b/cli/core/pkg/artifact/test/local/v0.0.1/tanzu-plugin-darwin_amd64 @@ -0,0 +1 @@ +plugin binary diff --git a/cli/core/pkg/artifact/test/local/v0.0.1/test/tanzu-test-plugin-darwin_amd64 b/cli/core/pkg/artifact/test/local/v0.0.1/test/tanzu-test-plugin-darwin_amd64 new file mode 100644 index 00000000000..b0ad6165c3f --- /dev/null +++ b/cli/core/pkg/artifact/test/local/v0.0.1/test/tanzu-test-plugin-darwin_amd64 @@ -0,0 +1 @@ +test plugin binary diff --git a/cli/core/pkg/artifact/test/local/v0.0.2/tanzu-plugin-darwin_amd64 b/cli/core/pkg/artifact/test/local/v0.0.2/tanzu-plugin-darwin_amd64 new file mode 100644 index 00000000000..b266d35e9e4 --- /dev/null +++ b/cli/core/pkg/artifact/test/local/v0.0.2/tanzu-plugin-darwin_amd64 @@ -0,0 +1 @@ +plugin binary diff --git a/cli/core/pkg/artifact/test/local/v0.0.2/test/extra_file b/cli/core/pkg/artifact/test/local/v0.0.2/test/extra_file new file mode 100644 index 00000000000..e69de29bb2d diff --git a/cli/core/pkg/artifact/test/local/v0.0.2/test/tanzu-test-plugin-darwin_amd64 b/cli/core/pkg/artifact/test/local/v0.0.2/test/tanzu-test-plugin-darwin_amd64 new file mode 100644 index 00000000000..b0ad6165c3f --- /dev/null +++ b/cli/core/pkg/artifact/test/local/v0.0.2/test/tanzu-test-plugin-darwin_amd64 @@ -0,0 +1 @@ +test plugin binary diff --git a/cli/core/pkg/cli/cmd.go b/cli/core/pkg/cli/cmd.go index 843c0bcb4e3..6c3ef7eb79d 100644 --- a/cli/core/pkg/cli/cmd.go +++ b/cli/core/pkg/cli/cmd.go @@ -140,9 +140,9 @@ func TestCmd(p *cliapi.PluginDescriptor) *cobra.Command { Use: p.Name, Short: p.Description, RunE: func(cmd *cobra.Command, args []string) error { - runner := NewRunner(p.Name, p.InstallationPath, args) + runner := NewRunner(p.Name, p.TestPluginInstallationPath, args) ctx := context.Background() - return runner.RunTest(ctx) + return runner.Run(ctx) }, DisableFlagParsing: true, } diff --git a/cli/core/pkg/distribution/artifact.go b/cli/core/pkg/distribution/artifact.go index 9bd2d29cdc9..12f97e07544 100644 --- a/cli/core/pkg/distribution/artifact.go +++ b/cli/core/pkg/distribution/artifact.go @@ -80,6 +80,27 @@ func (aMap Artifacts) Fetch(version, os, arch string) ([]byte, error) { "arch:%s", version, os, arch) } +// FetchTest the test binary for a plugin version. +func (aMap Artifacts) FetchTest(version, os, arch string) ([]byte, error) { + a, err := aMap.GetArtifact(version, os, arch) + if err != nil { + return nil, err + } + + if a.Image != "" { + return artifact.NewOCIArtifact(a.Image).FetchTest() + } + if a.URI != "" { + u, err := artifact.NewURIArtifact(a.URI) + if err != nil { + return nil, err + } + return u.FetchTest() + } + + return nil, errors.Errorf("invalid artifact for version:%s, os:%s, arch:%s", version, os, arch) +} + // GetDigest returns the SHA256 hash of the binary for a plugin version. func (aMap Artifacts) GetDigest(version, os, arch string) (string, error) { a, err := aMap.GetArtifact(version, os, arch) diff --git a/cli/core/pkg/distribution/interface.go b/cli/core/pkg/distribution/interface.go index 1da68641d75..fe23a8310e9 100644 --- a/cli/core/pkg/distribution/interface.go +++ b/cli/core/pkg/distribution/interface.go @@ -12,6 +12,9 @@ type Distribution interface { // Fetch the binary for a plugin version. Fetch(version, os, arch string) ([]byte, error) + // FetchTest the test binary for a plugin version. + FetchTest(version, os, arch string) ([]byte, error) + // GetDigest returns the SHA256 hash of the binary for a plugin version. GetDigest(version, os, arch string) (string, error) diff --git a/cli/core/pkg/pluginmanager/manager.go b/cli/core/pkg/pluginmanager/manager.go index 6c81020033b..3dd8858d868 100644 --- a/cli/core/pkg/pluginmanager/manager.go +++ b/cli/core/pkg/pluginmanager/manager.go @@ -488,6 +488,16 @@ func installOrUpgradePlugin(serverName string, p *plugin.Discovered, version str descriptor.Discovery = p.Source descriptor.DiscoveredRecommendedVersion = p.RecommendedVersion + b, err = p.Distribution.FetchTest(version, runtime.GOOS, runtime.GOARCH) + if err == nil { + testpluginPath := filepath.Join(common.DefaultPluginRoot, pluginName, fmt.Sprintf("test-%s", version)) + err = os.WriteFile(testpluginPath, b, 0755) + if err != nil { + return errors.Wrap(err, "error while saving test plugin binary") + } + descriptor.TestPluginInstallationPath = testpluginPath + } + c, err := catalog.NewContextCatalog(serverName) if err != nil { return err diff --git a/cli/runtime/apis/cli/v1alpha1/catalog_types.go b/cli/runtime/apis/cli/v1alpha1/catalog_types.go index a0aba4ec451..7924431f02e 100644 --- a/cli/runtime/apis/cli/v1alpha1/catalog_types.go +++ b/cli/runtime/apis/cli/v1alpha1/catalog_types.go @@ -141,6 +141,10 @@ type PluginDescriptor struct { // E.g., cluster/v0.3.2@sha256:... InstallationPath string `json:"installationPath"` + // TestPluginInstallationPath is a installation path for a test plugin binary. + // E.g., cluster/test-v0.3.2 + TestPluginInstallationPath string `json:"testPluginInstallationPath"` + // Discovery is the name of the discovery from where // this plugin is discovered. Discovery string `json:"discovery"` diff --git a/cmd/cli/plugin-admin/test/go.sum b/cmd/cli/plugin-admin/test/go.sum index 644d2e32072..21cff16369f 100644 --- a/cmd/cli/plugin-admin/test/go.sum +++ b/cmd/cli/plugin-admin/test/go.sum @@ -736,6 +736,7 @@ github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mo github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= +github.com/otiai10/copy v1.4.2 h1:RTiz2sol3eoXPLF4o+YWqEybwfUa/Q2Nkc4ZIUs3fwI= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= diff --git a/cmd/cli/plugin-admin/test/main.go b/cmd/cli/plugin-admin/test/main.go index 890bcb738bd..65b310426ad 100644 --- a/cmd/cli/plugin-admin/test/main.go +++ b/cmd/cli/plugin-admin/test/main.go @@ -4,15 +4,15 @@ package main import ( - "path/filepath" + "errors" "github.com/aunum/log" "github.com/spf13/cobra" "github.com/vmware-tanzu/tanzu-framework/cli/core/pkg/cli" + "github.com/vmware-tanzu/tanzu-framework/cli/core/pkg/pluginmanager" cliapi "github.com/vmware-tanzu/tanzu-framework/cli/runtime/apis/cli/v1alpha1" "github.com/vmware-tanzu/tanzu-framework/cli/runtime/buildinfo" - "github.com/vmware-tanzu/tanzu-framework/cli/runtime/config" "github.com/vmware-tanzu/tanzu-framework/cli/runtime/plugin" ) @@ -24,10 +24,10 @@ var descriptor = cliapi.PluginDescriptor{ BuildSHA: buildinfo.SHA, } -var local []string +var local string func init() { - fetchCmd.PersistentFlags().StringSliceVarP(&local, "local", "l", []string{}, "paths to local repository") + fetchCmd.PersistentFlags().StringVarP(&local, "local", "l", "", "paths to local repository") } func main() { @@ -35,17 +35,22 @@ func main() { if err != nil { log.Fatal(err) } + p.AddCommands( fetchCmd, pluginsCmd, ) - descs, err := cli.ListPlugins("test") + + _, standalonePlugins, err := pluginmanager.InstalledPlugins("") if err != nil { log.Fatal(err) } - for _, d := range descs { - pluginsCmd.AddCommand(cli.TestCmd(d)) + for _, d := range standalonePlugins { + if d.TestPluginInstallationPath == "" { + continue + } + pluginsCmd.AddCommand(cli.TestCmd(d.DeepCopy())) } if err := p.Execute(); err != nil { @@ -53,37 +58,18 @@ func main() { } } -var fetchCmd = &cobra.Command{ - Use: "fetch", - Short: "Fetch the plugin tests", - RunE: func(cmd *cobra.Command, args []string) error { - repos := getRepositories() - err := cli.EnsureTests(repos, "test") - if err != nil { - log.Fatal(err) - } - return nil - }, -} - var pluginsCmd = &cobra.Command{ Use: "plugin", Short: "Plugin tests", } -func getRepositories() *cli.MultiRepo { - if len(local) != 0 { - m := cli.NewMultiRepo() - for _, l := range local { - n := filepath.Base(l) - r := cli.NewLocalRepository(n, l) - m.AddRepository(r) +var fetchCmd = &cobra.Command{ + Use: "fetch", + Short: "Fetch the plugin tests", + RunE: func(cmd *cobra.Command, args []string) error { + if local == "" { + return errors.New("please specify paths to local repository containing test plugins with `--local` flag ") } - return m - } - cfg, err := config.GetClientConfig() - if err != nil { - log.Fatal(err) - } - return cli.NewMultiRepo(cli.LoadRepositories(cfg)...) + return pluginmanager.InstallPluginsFromLocalSource("all", "", local) + }, }