diff --git a/pkg/cli/arch.go b/pkg/cli/arch.go index 2a59f2461..f1d0aa4a2 100644 --- a/pkg/cli/arch.go +++ b/pkg/cli/arch.go @@ -15,6 +15,9 @@ var ( // AllOSArch defines all OS/ARCH combination for which plugin can be built AllOSArch = []Arch{LinuxAMD64, DarwinAMD64, WinAMD64, DarwinARM64, LinuxARM64} + + GOOS = runtime.GOOS + GOARCH = runtime.GOARCH ) // Arch represents a system architecture. @@ -22,7 +25,12 @@ type Arch string // BuildArch returns compile time build arch or locates it. func BuildArch() Arch { - return Arch(fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH)) + return Arch(fmt.Sprintf("%s_%s", GOOS, GOARCH)) +} + +func SetArch(a Arch) { + GOOS = a.OS() + GOARCH = a.Arch() } // IsWindows tells if an arch is windows. diff --git a/pkg/pluginmanager/manager.go b/pkg/pluginmanager/manager.go index 13028cea0..e717bd17a 100644 --- a/pkg/pluginmanager/manager.go +++ b/pkg/pluginmanager/manager.go @@ -11,7 +11,6 @@ import ( "os" "os/exec" "path/filepath" - "runtime" "strings" "github.com/pkg/errors" @@ -486,8 +485,8 @@ func installPlugin(pluginName, version string, target configtypes.Target, contex Name: pluginName, Target: target, Version: version, - OS: runtime.GOOS, - Arch: runtime.GOARCH, + OS: cli.GOOS, + Arch: cli.GOARCH, } errorList := make([]error, 0) availablePlugins, err := discoverSpecificPlugins(discoveries, discovery.WithPluginDiscoveryCriteria(criteria)) @@ -495,6 +494,21 @@ func installPlugin(pluginName, version string, target configtypes.Target, contex errorList = append(errorList, err) } + // If we cannot find the plugin for DarwinARM64, let's fallback to DarwinAMD64. + // This leverages Apples Rosetta emulator and helps until plugins are all available + // for ARM64. Note that this cannot be used on Linux since there is no such emulator. + if len(availablePlugins) == 0 && cli.BuildArch() == cli.DarwinARM64 { + // Pretend we are on a AMD64 machine + cli.SetArch(cli.DarwinAMD64) + defer cli.SetArch(cli.DarwinARM64) // Go back to ARM64 once the plugin is installed + + criteria.Arch = cli.DarwinAMD64.Arch() + availablePlugins, err = discoverSpecificPlugins(discoveries, discovery.WithPluginDiscoveryCriteria(criteria)) + if err != nil { + errorList = append(errorList, err) + } + } + if len(availablePlugins) == 0 { if target != configtypes.TargetUnknown { errorList = append(errorList, errors.Errorf("unable to find plugin '%v' matching version '%v' for target '%s'", pluginName, version, string(target))) @@ -689,7 +703,7 @@ func installOrUpgradePlugin(p *discovery.Discovered, version string, installTest } func getPluginFromCache(p *discovery.Discovered, version string) *cli.PluginInfo { - pluginArtifact, err := p.Distribution.DescribeArtifact(version, runtime.GOOS, runtime.GOARCH) + pluginArtifact, err := p.Distribution.DescribeArtifact(version, cli.GOOS, cli.GOARCH) if err != nil { return nil } @@ -721,13 +735,13 @@ func fetchAndVerifyPlugin(p *discovery.Discovered, version string) ([]byte, erro return nil, errors.Wrapf(err, "%q plugin pre-download verification failed", p.Name) } - b, err := p.Distribution.Fetch(version, runtime.GOOS, runtime.GOARCH) + b, err := p.Distribution.Fetch(version, cli.GOOS, cli.GOARCH) if err != nil { return nil, errors.Wrapf(err, "unable to fetch the plugin metadata for plugin %q", p.Name) } // verify plugin after download but before installation - d, err := p.Distribution.GetDigest(version, runtime.GOOS, runtime.GOARCH) + d, err := p.Distribution.GetDigest(version, cli.GOOS, cli.GOARCH) if err != nil { return nil, err } @@ -782,7 +796,7 @@ func describePlugin(p *discovery.Discovered, pluginPath string) (*cli.PluginInfo func doInstallTestPlugin(p *discovery.Discovered, pluginPath, version string) error { log.Infof("Installing test plugin for '%v:%v'", p.Name, version) - binary, err := p.Distribution.FetchTest(version, runtime.GOOS, runtime.GOARCH) + binary, err := p.Distribution.FetchTest(version, cli.GOOS, cli.GOARCH) if err != nil { if os.Getenv("TZ_ENFORCE_TEST_PLUGIN") == "1" { return errors.Wrapf(err, "unable to install test plugin for '%v:%v'", p.Name, version) @@ -1149,8 +1163,8 @@ func getCLIPluginResourceWithLocalDistroFromPluginInfo(plugin *cli.PluginInfo, p { URI: pluginBinaryPath, Type: common.DistributionTypeLocal, - OS: runtime.GOOS, - Arch: runtime.GOARCH, + OS: cli.GOOS, + Arch: cli.GOARCH, }, }, }, @@ -1213,7 +1227,7 @@ func discoverPluginsFromLocalSourceBasedOnManifestFile(localPath string) ([]disc // Sample path: cli/[target]///tanzu--_ // cli/[target]/v0.14.0/tanzu-login-darwin_amd64 // As mentioned above, we expect the binary for user's OS-ARCH is present and hence creating path accordingly - pluginBinaryPath := filepath.Join(absLocalPath, string(pluginInfo.Target), p.Name, pluginInfo.Version, fmt.Sprintf("tanzu-%s-%s_%s", p.Name, runtime.GOOS, runtime.GOARCH)) + pluginBinaryPath := filepath.Join(absLocalPath, string(pluginInfo.Target), p.Name, pluginInfo.Version, fmt.Sprintf("tanzu-%s-%s_%s", p.Name, cli.GOOS, cli.GOARCH)) if cli.BuildArch().IsWindows() { pluginBinaryPath += exe } @@ -1272,7 +1286,7 @@ func getPluginInfoResource(pluginFilePath string) (*cli.PluginInfo, error) { // verifyPluginPreDownload verifies that the plugin distribution repo is trusted // and returns error if the verification fails. func verifyPluginPreDownload(p *discovery.Discovered, version string) error { - artifactInfo, err := p.Distribution.DescribeArtifact(version, runtime.GOOS, runtime.GOARCH) + artifactInfo, err := p.Distribution.DescribeArtifact(version, cli.GOOS, cli.GOARCH) if err != nil { return err } @@ -1282,7 +1296,7 @@ func verifyPluginPreDownload(p *discovery.Discovered, version string) error { if artifactInfo.URI != "" { return verifyArtifactLocation(artifactInfo.URI) } - return errors.Errorf("no download information available for artifact \"%s:%s:%s:%s\"", p.Name, p.RecommendedVersion, runtime.GOOS, runtime.GOARCH) + return errors.Errorf("no download information available for artifact \"%s:%s:%s:%s\"", p.Name, p.RecommendedVersion, cli.GOOS, cli.GOARCH) } // verifyRegistry verifies the authenticity of the registry from where cli is diff --git a/pkg/pluginmanager/manager_helper_test.go b/pkg/pluginmanager/manager_helper_test.go index 7ea329858..219a2c198 100644 --- a/pkg/pluginmanager/manager_helper_test.go +++ b/pkg/pluginmanager/manager_helper_test.go @@ -32,6 +32,7 @@ var testPlugins = []plugininventory.PluginIdentifier{ {Name: "cluster", Target: configtypes.TargetK8s, Version: "v1.6.0"}, {Name: "myplugin", Target: configtypes.TargetK8s, Version: "v1.6.0"}, {Name: "feature", Target: configtypes.TargetK8s, Version: "v0.2.0"}, + {Name: "pluginwitharm", Target: configtypes.TargetK8s, Version: "v2.0.0"}, {Name: "isolated-cluster", Target: configtypes.TargetGlobal, Version: "v1.2.3"}, {Name: "isolated-cluster", Target: configtypes.TargetGlobal, Version: "v1.3.0"}, @@ -47,6 +48,10 @@ var testPlugins = []plugininventory.PluginIdentifier{ {Name: "myplugin", Target: configtypes.TargetTMC, Version: "v0.2.0"}, } +var testPluginsNoARM64 = []plugininventory.PluginIdentifier{ + {Name: "pluginnoarm", Target: configtypes.TargetK8s, Version: "v1.0.0"}, +} + const createGroupsStmt = ` INSERT INTO PluginGroups VALUES( 'vmware', @@ -115,6 +120,10 @@ INSERT INTO PluginGroups VALUES( 'true', 'false'); ` +const ( + digestForAMD64 = "0000000000" + digestForARM64 = "1111111111" +) func findDiscoveredPlugin(discovered []discovery.Discovered, pluginName string, target configtypes.Target) *discovery.Discovered { for i := range discovered { @@ -185,20 +194,39 @@ func setupPluginBinaryInCache(name, version string, target configtypes.Target, d } } +func createPluginEntry(db *sql.DB, plugin plugininventory.PluginIdentifier, arch cli.Arch, digest string) { + uri := fmt.Sprintf("vmware/test/%s/%s/%s/%s:%s", arch.OS(), arch.Arch(), plugin.Target, plugin.Name, plugin.Version) + desc := fmt.Sprintf("Plugin %s description", plugin.Name) + + _, err := db.Exec("INSERT INTO PluginBinaries (PluginName,Target,RecommendedVersion,Version,Hidden,Description,Publisher,Vendor,OS,Architecture,Digest,URI) VALUES(?,?,'',?,'false',?,'test','vmware',?,?,?,?);", plugin.Name, plugin.Target, plugin.Version, desc, arch.OS(), arch.Arch(), digest, uri) + + if err != nil { + log.Fatal(err, fmt.Sprintf("failed to create %s:%s for target %s for testing", plugin.Name, plugin.Version, plugin.Target)) + } +} + func setupPluginEntriesAndBinaries(db *sql.DB) { - digest := "0000000000" + // Setup DB entries and plugin binaries for all os/architecture combinations for _, plugin := range testPlugins { - desc := fmt.Sprintf("Plugin %s description", plugin.Name) for _, osArch := range cli.AllOSArch { - uri := fmt.Sprintf("vmware/test/%s/%s/%s/%s:%s", osArch.OS(), osArch.Arch(), plugin.Target, plugin.Name, plugin.Version) - - _, err := db.Exec("INSERT INTO PluginBinaries (PluginName,Target,RecommendedVersion,Version,Hidden,Description,Publisher,Vendor,OS,Architecture,Digest,URI) VALUES(?,?,'',?,'false',?,'test','vmware',?,?,?,?);", plugin.Name, plugin.Target, plugin.Version, desc, osArch.OS(), osArch.Arch(), digest, uri) + digest := digestForAMD64 + if osArch.Arch() == cli.DarwinARM64.Arch() { + digest = digestForARM64 + } + createPluginEntry(db, plugin, osArch, digest) + setupPluginBinaryInCache(plugin.Name, plugin.Version, plugin.Target, digest) + } + } - if err != nil { - log.Fatal(err, fmt.Sprintf("failed to create %s:%s for target %s for testing", plugin.Name, plugin.Version, plugin.Target)) + // Setup DB entries and plugin binaries but skip ARM64 + digest := digestForAMD64 + for _, plugin := range testPluginsNoARM64 { + for _, osArch := range cli.AllOSArch { + if osArch.Arch() != cli.DarwinARM64.Arch() { + createPluginEntry(db, plugin, osArch, digest) + setupPluginBinaryInCache(plugin.Name, plugin.Version, plugin.Target, digest) } } - setupPluginBinaryInCache(plugin.Name, plugin.Version, plugin.Target, digest) } } diff --git a/pkg/pluginmanager/manager_test.go b/pkg/pluginmanager/manager_test.go index 381acfa34..9a6880cba 100644 --- a/pkg/pluginmanager/manager_test.go +++ b/pkg/pluginmanager/manager_test.go @@ -130,6 +130,24 @@ var expectedDiscoveredStandalonePlugins = []discovery.Discovered{ ContextName: "", Target: configtypes.TargetTMC, }, + { + Name: "pluginnoarm", + Description: "Plugin pluginnoarm description", + RecommendedVersion: "v1.0.0", + SupportedVersions: []string{"v1.0.0"}, + Scope: common.PluginScopeStandalone, + ContextName: "", + Target: configtypes.TargetK8s, + }, + { + Name: "pluginwitharm", + Description: "Plugin pluginwitharm description", + RecommendedVersion: "v2.0.0", + SupportedVersions: []string{"v2.0.0"}, + Scope: common.PluginScopeStandalone, + ContextName: "", + Target: configtypes.TargetK8s, + }, } var expectedDiscoveredGroups = []string{"vmware-test/default:v1.6.0", "vmware-test/default:v2.1.0"} @@ -266,10 +284,37 @@ func Test_InstallStandalonePlugin(t *testing.T) { assertions.NotNil(err) assertions.Contains(err.Error(), "unable to find plugin 'feature' matching version 'v0.2.0' for target 'mission-control'") + // When on Darwin ARM64, try installing a plugin that is only available for Darwin AMD64 + // and see that it still gets installed (it will use AMD64) + // + // First make the CLI believe we are running on Darwin ARM64. We need this for when + // the unit tests are run on Linux for example. + realArch := cli.BuildArch() + cli.SetArch(cli.DarwinARM64) + err = InstallStandalonePlugin("pluginnoarm", "v1.0.0", configtypes.TargetUnknown) + assertions.Nil(err) + // Make sure that after the plugin is installed (using AMD64), the arch is back to ARM64 + assertions.Equal(cli.DarwinARM64, cli.BuildArch()) + // Now reset to the real machine architecture + cli.SetArch(realArch) + + // When on Darwin ARM64, try installing a plugin that IS available for Darwin ARM64 + // and make sure it is the ARM64 one that gets installed (not AMD64) + // + // First make the CLI believe we are running on Darwin ARM64. We need this for when + // the unit tests are run on Linux for example. + cli.SetArch(cli.DarwinARM64) + err = InstallStandalonePlugin("pluginwitharm", "v2.0.0", configtypes.TargetUnknown) + assertions.Nil(err) + // Make sure that after the plugin is installed (using AMD64), the arch is back to ARM64 + assertions.Equal(cli.DarwinARM64, cli.BuildArch()) + // Now reset to the real machine architecture + cli.SetArch(realArch) + // Verify installed plugins installedStandalonePlugins, err := pluginsupplier.GetInstalledStandalonePlugins() assertions.Nil(err) - assertions.Equal(4, len(installedStandalonePlugins)) + assertions.Equal(6, len(installedStandalonePlugins)) installedServerPlugins, err := pluginsupplier.GetInstalledServerPlugins() assertions.Nil(err) assertions.Equal(0, len(installedServerPlugins)) @@ -299,12 +344,34 @@ func Test_InstallStandalonePlugin(t *testing.T) { Scope: common.PluginScopeStandalone, Target: configtypes.TargetTMC, }, + { + Name: "pluginnoarm", + Version: "v1.0.0", + Scope: common.PluginScopeStandalone, + Target: configtypes.TargetK8s, + }, + { + Name: "pluginwitharm", + Version: "v2.0.0", + Scope: common.PluginScopeStandalone, + Target: configtypes.TargetK8s, + }, } for i := 0; i < len(expectedInstalledStandalonePlugins); i++ { pd := findPluginInfo(installedStandalonePlugins, expectedInstalledStandalonePlugins[i].Name, expectedInstalledStandalonePlugins[i].Target) assertions.NotNil(pd) assertions.Equal(expectedInstalledStandalonePlugins[i].Version, pd.Version) + + if pd.Name == "pluginnoarm" { + // Make sure this plugin is always installed as AMD64 + assertions.Equal(digestForAMD64, pd.Digest) + } + + if pd.Name == "pluginwitharm" { + // Make sure this plugin is always installed as ARM64 + assertions.Equal(digestForARM64, pd.Digest) + } } } diff --git a/pkg/registry/helpers.go b/pkg/registry/helpers.go index 6e7cde272..db70bbaa2 100644 --- a/pkg/registry/helpers.go +++ b/pkg/registry/helpers.go @@ -11,7 +11,6 @@ import ( "net" "net/http" "os" - "runtime" "strconv" "time" @@ -19,6 +18,7 @@ import ( gocontainerregistry "github.com/google/go-containerregistry/pkg/registry" "github.com/pkg/errors" + "github.com/vmware-tanzu/tanzu-cli/pkg/cli" "github.com/vmware-tanzu/tanzu-cli/pkg/configpaths" "github.com/vmware-tanzu/tanzu-cli/pkg/constants" configlib "github.com/vmware-tanzu/tanzu-plugin-runtime/config" @@ -38,7 +38,7 @@ func GetRegistryCertOptions(registryHost string) (*CertOptions, error) { Insecure: false, } - if runtime.GOOS == "windows" { + if cli.GOOS == "windows" { err := AddRegistryTrustedRootCertsFileForWindows(registryCertOpts) if err != nil { return nil, err diff --git a/pkg/telemetry/sqlite_metrics_db.go b/pkg/telemetry/sqlite_metrics_db.go index 849e584ee..503c4a521 100644 --- a/pkg/telemetry/sqlite_metrics_db.go +++ b/pkg/telemetry/sqlite_metrics_db.go @@ -7,7 +7,6 @@ import ( "database/sql" "os" "path/filepath" - "runtime" "strconv" // Import the sqlite3 driver @@ -15,6 +14,7 @@ import ( "github.com/pkg/errors" + "github.com/vmware-tanzu/tanzu-cli/pkg/cli" "github.com/vmware-tanzu/tanzu-cli/pkg/common" ) @@ -110,8 +110,8 @@ func (b *sqliteMetricsDB) SaveOperationMetric(entry *OperationMetricsPayload) er row := cliOperationsRow{ cliVersion: entry.CliVersion, - osName: runtime.GOOS, - osArch: runtime.GOARCH, + osName: cli.GOOS, + osArch: cli.GOARCH, pluginName: entry.PluginName, pluginVersion: entry.PluginVersion, command: entry.CommandName, diff --git a/pkg/telemetry/sqlite_metrics_db_test.go b/pkg/telemetry/sqlite_metrics_db_test.go index 73dd9c30f..c9e6e4ec3 100644 --- a/pkg/telemetry/sqlite_metrics_db_test.go +++ b/pkg/telemetry/sqlite_metrics_db_test.go @@ -7,13 +7,14 @@ import ( "database/sql" "os" "path/filepath" - "runtime" "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/pkg/errors" + + "github.com/vmware-tanzu/tanzu-cli/pkg/cli" ) var _ = Describe("Inserting CLI metrics to database and verifying it by fetching the metrics from database", func() { @@ -65,8 +66,8 @@ var _ = Describe("Inserting CLI metrics to database and verifying it by fetching Expect(len(metricsRows)).To(Equal(1)) Expect(metricsRows[0].cliID).To(Equal("fake-cli-cliID")) Expect(metricsRows[0].cliVersion).To(Equal("v1.0.0")) - Expect(metricsRows[0].osName).To(Equal(runtime.GOOS)) - Expect(metricsRows[0].osArch).To(Equal(runtime.GOARCH)) + Expect(metricsRows[0].osName).To(Equal(cli.GOOS)) + Expect(metricsRows[0].osArch).To(Equal(cli.GOARCH)) Expect(metricsRows[0].nameArg).To(Equal("fake-name-arg")) Expect(metricsRows[0].command).To(Equal("fake-cmd-name")) Expect(metricsRows[0].commandStartTSMsec).ToNot(BeEmpty())