diff --git a/cli/azd/.vscode/cspell-azd-dictionary.txt b/cli/azd/.vscode/cspell-azd-dictionary.txt index a839690329..a230fdd7b1 100644 --- a/cli/azd/.vscode/cspell-azd-dictionary.txt +++ b/cli/azd/.vscode/cspell-azd-dictionary.txt @@ -94,6 +94,7 @@ keychain LASTEXITCODE ldflags lechnerc77 +libc mgmt mgutz microsoftonline diff --git a/cli/azd/pkg/tools/bicep/bicep.go b/cli/azd/pkg/tools/bicep/bicep.go index 038bf955e2..b4331c7e89 100644 --- a/cli/azd/pkg/tools/bicep/bicep.go +++ b/cli/azd/pkg/tools/bicep/bicep.go @@ -165,11 +165,11 @@ func downloadBicep(ctx context.Context, transporter policy.Transporter, bicepVer case "darwin": releaseName = fmt.Sprintf("bicep-osx-%s", arch) case "linux": - if _, err := os.Stat("/lib/ld-musl-x86_64.so.1"); err == nil { - // As of 0.14.46, there is no version of for AM64 on musl based systems. - if arch == "arm64" { + if preferMuslBicep(os.Stat) { + if runtime.GOARCH != "arm64" { return fmt.Errorf("unsupported architecture: %s", runtime.GOARCH) } + releaseName = "bicep-linux-musl-x64" } else { releaseName = fmt.Sprintf("bicep-linux-%s", arch) @@ -228,6 +228,23 @@ func downloadBicep(ctx context.Context, transporter policy.Transporter, bicepVer return nil } +type stater func(name string) (os.FileInfo, error) + +// preferMuslBicep determines if we should install the version of bicep that used musl instead of glibc. We prefer +// musl bicep on linux systems that have musl installed and do not have glibc installed. If both musl and glibc are +// installed, we prefer the glibc based version. This behavior matches the `az` CLI (see: Azure/azure-cli#23040) +func preferMuslBicep(stat stater) bool { + if _, err := stat("/lib/ld-musl-x86_64.so.1"); err == nil { + if _, err := stat("/lib/x86_64-linux-gnu/libc.so.6"); err == nil { + return false + } + + return true + } + + return false +} + func (cli *bicepCli) version(ctx context.Context) (semver.Version, error) { bicepRes, err := cli.runCommand(ctx, nil, "--version") if err != nil { diff --git a/cli/azd/pkg/tools/bicep/bicep_test.go b/cli/azd/pkg/tools/bicep/bicep_test.go index 3700ca6a9a..df2071ea4a 100644 --- a/cli/azd/pkg/tools/bicep/bicep_test.go +++ b/cli/azd/pkg/tools/bicep/bicep_test.go @@ -10,12 +10,14 @@ import ( "path/filepath" "strings" "testing" + "time" "github.com/azure/azure-dev/cli/azd/pkg/exec" "github.com/azure/azure-dev/cli/azd/pkg/input" "github.com/azure/azure-dev/cli/azd/pkg/osutil" "github.com/azure/azure-dev/cli/azd/test/mocks" "github.com/azure/azure-dev/cli/azd/test/mocks/mockinput" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -137,3 +139,75 @@ func TestNewBicepCliWillUpgrade(t *testing.T) { require.Equal(t, []byte(NEW_FILE_CONTENTS), contents) } + +func Test_preferMuslBicep(t *testing.T) { + tests := []struct { + name string + hasMusl bool + hasGlibc bool + want bool + }{ + { + name: "musl preferred", + hasMusl: true, + hasGlibc: false, + want: true, + }, + { + name: "glibc preferred", + hasMusl: false, + hasGlibc: true, + want: false, + }, + { + name: "both available", + hasMusl: true, + hasGlibc: true, + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockStat := func(name string) (os.FileInfo, error) { + if tt.hasMusl && name == "/lib/ld-musl-x86_64.so.1" { + return &fakeFileInfo{}, nil + } + if tt.hasGlibc && name == "/lib/x86_64-linux-gnu/libc.so.6" { + return &fakeFileInfo{}, nil + } + + return nil, os.ErrNotExist + } + got := preferMuslBicep(mockStat) + assert.Equal(t, tt.want, got) + }) + } +} + +type fakeFileInfo struct { +} + +func (f *fakeFileInfo) Name() string { + return "" +} + +func (f *fakeFileInfo) Size() int64 { + return 0 +} + +func (f *fakeFileInfo) Mode() os.FileMode { + return 0 +} + +func (f *fakeFileInfo) ModTime() time.Time { + return time.Time{} +} + +func (f *fakeFileInfo) IsDir() bool { + return false +} + +func (f *fakeFileInfo) Sys() interface{} { + return nil +}