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

Fallback to installing AMD64 plugins on Darwin #491

Merged
Merged
Show file tree
Hide file tree
Changes from all 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
10 changes: 9 additions & 1 deletion pkg/cli/arch.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,22 @@ 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.
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.
Expand Down
38 changes: 26 additions & 12 deletions pkg/pluginmanager/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"

"github.com/pkg/errors"
Expand Down Expand Up @@ -486,15 +485,30 @@ 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))
if err != nil {
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)))
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
},
},
},
Expand Down Expand Up @@ -1213,7 +1227,7 @@ func discoverPluginsFromLocalSourceBasedOnManifestFile(localPath string) ([]disc
// Sample path: cli/[target]/<plugin-name>/<plugin-version>/tanzu-<plugin-name>-<os>_<arch>
// 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
}
Expand Down Expand Up @@ -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
}
Expand All @@ -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
Expand Down
44 changes: 36 additions & 8 deletions pkg/pluginmanager/manager_helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
Expand All @@ -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',
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
}

Expand Down
69 changes: 68 additions & 1 deletion pkg/pluginmanager/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"}
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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)
}
}
}

Expand Down
4 changes: 2 additions & 2 deletions pkg/registry/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ import (
"net"
"net/http"
"os"
"runtime"
"strconv"
"time"

regname "github.com/google/go-containerregistry/pkg/name"
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"
Expand All @@ -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
Expand Down
6 changes: 3 additions & 3 deletions pkg/telemetry/sqlite_metrics_db.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import (
"database/sql"
"os"
"path/filepath"
"runtime"
"strconv"

// Import the sqlite3 driver
_ "modernc.org/sqlite"

"github.com/pkg/errors"

"github.com/vmware-tanzu/tanzu-cli/pkg/cli"
"github.com/vmware-tanzu/tanzu-cli/pkg/common"
)

Expand Down Expand Up @@ -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,
Expand Down
7 changes: 4 additions & 3 deletions pkg/telemetry/sqlite_metrics_db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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())
Expand Down