VictoriaMetrics/lib/backup/azremote/azblob_test.go
justinrush e65e55e2dd
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 5fd3aef549)
2024-07-10 12:26:21 +02:00

161 lines
3.7 KiB
Go

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
}