VictoriaMetrics/lib/backup/azremote/azblob_test.go

161 lines
3.7 KiB
Go
Raw Normal View History

lib/backup: add support for Azure Managed Identity (#6518) ### Describe Your Changes These changes support using Azure Managed Identity for the `vmbackup` utility. It adds two new environment variables: * `AZURE_USE_DEFAULT_CREDENTIAL`: Instructs the `vmbackup` utility to build a connection using the [Azure Default Credential](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity@v1.5.2#NewDefaultAzureCredential) mode. This causes the Azure SDK to check for a variety of environment variables to try and make a connection. By default, it tries to use managed identity if that is set up. This will close https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5984 ### Checklist The following checks are **mandatory**: - [x] My change adheres [VictoriaMetrics contributing guidelines](https://docs.victoriametrics.com/contributing/). ### Testing However you normally test the `vmbackup` utility using Azure Blob should continue to work without any changes. The set up for that is environment specific and not listed out here. Once regression testing has been done you can set up [Azure Managed Identity](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/overview) so your resource (AKS, VM, etc), can use that credential method. Once it is set up, update your environment variables according to the updated documentation. I added unit tests to the `FS.Init` function, then made my changes, then updated the unit tests to capture the new branches. I tested this in our environment, but with SAS token auth and managed identity and it works as expected. --------- Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com> Co-authored-by: Justin Rush <jarush@epic.com> Co-authored-by: Zakhar Bessarab <z.bessarab@victoriametrics.com> Co-authored-by: hagen1778 <roman@victoriametrics.com> (cherry picked from commit 5fd3aef54954c9744a370c5c11e1639cc52049e1)
2024-07-10 11:52:05 +02:00
package azremote
import (
"bytes"
"errors"
"strings"
"testing"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
)
func Test_cleanDirectory(t *testing.T) {
cases := map[string]struct {
Dir string
ExpectedDir string
}{
"dir / prefix is removed": {
Dir: "/foo/",
ExpectedDir: "foo/",
},
"multiple dir prefix / is removed": {
Dir: "//foo/",
ExpectedDir: "foo/",
},
"suffix is added": {
Dir: "foo",
ExpectedDir: "foo/",
},
}
for name, test := range cases {
t.Run(name, func(t *testing.T) {
dir := cleanDirectory(test.Dir)
if dir != test.ExpectedDir {
t.Errorf("expected dir %q, got %q", test.ExpectedDir, dir)
}
})
}
}
func Test_FSInit(t *testing.T) {
cases := map[string]struct {
IgnoreFakeEnv bool
Env testEnv
ExpectedErr error
ExpectedLogs []string
}{
"connection string env var is used": {
Env: map[string]string{
envStorageAccCs: "BlobEndpoint=https://test.blob.core.windows.net/;SharedAccessSignature=",
},
ExpectedLogs: []string{`Creating AZBlob service client from connection string`},
},
"base envtemplate package is used and connection string err bubbles": {
IgnoreFakeEnv: true,
Env: map[string]string{
envStorageAccCs: "BlobEndpoint=https://test.blob.core.windows.net/;SharedAccessSignature=",
},
ExpectedErr: errNoCredentials,
},
"only storage account name is an err": {
Env: map[string]string{
envStorageAcctName: "test",
},
ExpectedErr: errNoCredentials,
},
"uses shared key credential": {
Env: map[string]string{
envStorageAcctName: "test",
envStorageAccKey: "dGVhcG90Cg==",
},
ExpectedLogs: []string{`Creating AZBlob service client from account name and key`},
},
"allows overriding domain name with account name and key": {
Env: map[string]string{
envStorageAcctName: "test",
envStorageAccKey: "dGVhcG90Cg==",
envStorageDomain: "foo.bar",
},
ExpectedLogs: []string{
`Creating AZBlob service client from account name and key`,
`Overriding default Azure blob domain with "foo.bar"`,
},
},
"can't specify both connection string and shared key": {
Env: map[string]string{
envStorageAccCs: "teapot",
envStorageAcctName: "test",
envStorageAccKey: "dGVhcG90Cg==",
},
ExpectedErr: errInvalidCredentials,
},
"just use default is an err": {
Env: map[string]string{
envStorageDefault: "true",
},
ExpectedErr: errNoCredentials,
},
"uses default credential": {
Env: map[string]string{
envStorageDefault: "true",
envStorageAcctName: "test",
},
ExpectedLogs: []string{`Creating AZBlob service client from default credential`},
},
}
for name, test := range cases {
t.Run(name, func(t *testing.T) {
tlog := &testLogger{}
logger.SetOutputForTests(tlog)
t.Cleanup(logger.ResetOutputForTest)
fs := &FS{Dir: "foo"}
if test.Env != nil && !test.IgnoreFakeEnv {
fs.env = test.Env.LookupEnv
}
err := fs.Init()
if err != nil && !errors.Is(err, test.ExpectedErr) {
t.Errorf("expected error %q, got %q", test.ExpectedErr, err)
}
tlog.MustContain(t, test.ExpectedLogs...)
})
}
}
type testLogger struct {
buf *bytes.Buffer
}
func (l *testLogger) Write(p []byte) (n int, err error) {
if l.buf == nil {
l.buf = &bytes.Buffer{}
}
return l.buf.Write(p)
}
func (l *testLogger) MustContain(t *testing.T, vals ...string) {
t.Helper()
contents := l.buf.String()
for _, val := range vals {
if !strings.Contains(contents, val) {
t.Errorf("expected log to contain %q, got %q", val, l.buf.String())
}
}
}
type testEnv map[string]string
func (e testEnv) LookupEnv(key string) (string, bool) {
val, ok := e[key]
return val, ok
}