From 178fe262e55e04098303da144f91631b0ee1803e Mon Sep 17 00:00:00 2001 From: Matthew Solomon <89044647+MBSolomon@users.noreply.github.com> Date: Thu, 14 Dec 2023 14:38:11 -0800 Subject: [PATCH] Add Snapshot support for SDK (#22118) * remove generated * Make base recording for snapshot tests. Customizable for local testing * comment updates * update test proxy * Changes to Operation-Local header for test Proxy * add comments * add comments * fix regional link * changelog updates * update changelog * update version.go * comment updates * renamed variables/pre-make maps with known sizes for pager items * omit pager from response/options --- sdk/data/azappconfig/CHANGELOG.md | 7 +- sdk/data/azappconfig/assets.json | 2 +- sdk/data/azappconfig/client.go | 289 +++++++++++++++ sdk/data/azappconfig/client_test.go | 331 ++++++++++++++++++ sdk/data/azappconfig/constants.go | 70 +++- sdk/data/azappconfig/examples_test.go | 163 +++++++++ sdk/data/azappconfig/go.mod | 2 +- sdk/data/azappconfig/go.sum | 4 +- .../internal/generated/custom_client.go | 16 + sdk/data/azappconfig/keyvaluefilter.go | 16 + sdk/data/azappconfig/options.go | 106 ++++++ sdk/data/azappconfig/response_types.go | 84 +++++ sdk/data/azappconfig/setting_selector.go | 9 + sdk/data/azappconfig/snapshot.go | 82 +++++ sdk/data/azappconfig/utils_test.go | 8 + sdk/data/azappconfig/version.go | 2 +- 16 files changed, 1180 insertions(+), 11 deletions(-) create mode 100644 sdk/data/azappconfig/keyvaluefilter.go create mode 100644 sdk/data/azappconfig/snapshot.go diff --git a/sdk/data/azappconfig/CHANGELOG.md b/sdk/data/azappconfig/CHANGELOG.md index 9bef2167d7..771db50fb7 100644 --- a/sdk/data/azappconfig/CHANGELOG.md +++ b/sdk/data/azappconfig/CHANGELOG.md @@ -1,12 +1,9 @@ # Release History -## 1.0.1 (Unreleased) +## 1.1.0 (2024-01-16) ### Features Added - -### Breaking Changes - -### Bugs Fixed +* Added support for [`Snapshots`](https://learn.microsoft.com/azure/azure-app-configuration/concept-snapshots). ### Other Changes * Updated to latest version of `azcore`. diff --git a/sdk/data/azappconfig/assets.json b/sdk/data/azappconfig/assets.json index e8c3f553d8..9134cad9e7 100644 --- a/sdk/data/azappconfig/assets.json +++ b/sdk/data/azappconfig/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "go", "TagPrefix": "go/data/azappconfig", - "Tag": "go/data/azappconfig_ec954b99e7" + "Tag": "go/data/azappconfig_ff59e7b261" } diff --git a/sdk/data/azappconfig/client.go b/sdk/data/azappconfig/client.go index d0fdf411b4..39666e70dc 100644 --- a/sdk/data/azappconfig/client.go +++ b/sdk/data/azappconfig/client.go @@ -275,3 +275,292 @@ func (c *Client) NewListSettingsPager(selector SettingSelector, options *ListSet Tracer: c.appConfigClient.Tracer(), }) } + +// NewListSnapshotsPager - Gets a list of key-value snapshots. +// +// - options - NewListSnapshotsPagerOptions contains the optional parameters to retrieve a snapshot +// method. +func (c *Client) NewListSnapshotsPager(options *ListSnapshotsOptions) *runtime.Pager[ListSnapshotsResponse] { + opts := (*generated.AzureAppConfigurationClientGetSnapshotsOptions)(options) + ssRespPager := c.appConfigClient.NewGetSnapshotsPager(opts) + + return runtime.NewPager(runtime.PagingHandler[ListSnapshotsResponse]{ + More: func(ListSnapshotsResponse) bool { + return ssRespPager.More() + }, + Fetcher: func(ctx context.Context, cur *ListSnapshotsResponse) (ListSnapshotsResponse, error) { + page, err := ssRespPager.NextPage(ctx) + if err != nil { + return ListSnapshotsResponse{}, err + } + + snapshots := make([]Snapshot, len(page.Items)) + + for i := 0; i < len(page.Items); i++ { + + snapshot := page.Items[i] + + convertedETag := azcore.ETag(*snapshot.Etag) + + convertedFilters := make([]KeyValueFilter, len(snapshot.Filters)) + + for j := 0; j < len(snapshot.Filters); j++ { + convertedFilters[j] = KeyValueFilter{ + Key: snapshot.Filters[j].Key, + Label: snapshot.Filters[j].Label, + } + } + + snapshots[i] = Snapshot{ + Filters: convertedFilters, + CompositionType: snapshot.CompositionType, + RetentionPeriod: snapshot.RetentionPeriod, + Tags: snapshot.Tags, + Created: snapshot.Created, + ETag: &convertedETag, + Expires: snapshot.Expires, + ItemsCount: snapshot.ItemsCount, + Name: snapshot.Name, + Size: snapshot.Size, + Status: snapshot.Status, + } + } + + return ListSnapshotsResponse{ + Snapshots: snapshots, + SyncToken: SyncToken(*page.SyncToken), + }, nil + }, + Tracer: c.appConfigClient.Tracer(), + }) +} + +// NewListSettingsForSnapshotPager +// +// - snapshotName - The name of the snapshot to list configuration settings for +// - options - ListSettingsForSnapshotOptions contains the optional parameters to retrieve Snapshot configuration settings +func (c *Client) NewListSettingsForSnapshotPager(snapshotName string, options *ListSettingsForSnapshotOptions) *runtime.Pager[ListSettingsForSnapshotResponse] { + if options == nil { + options = &ListSettingsForSnapshotOptions{} + } + + opts := generated.AzureAppConfigurationClientGetKeyValuesOptions{ + AcceptDatetime: options.AcceptDatetime, + After: options.After, + IfMatch: options.IfMatch, + IfNoneMatch: options.IfNoneMatch, + Select: options.Select, + Snapshot: &snapshotName, + Key: &options.Key, + Label: &options.Label, + } + ssRespPager := c.appConfigClient.NewGetKeyValuesPager(&opts) + + return runtime.NewPager(runtime.PagingHandler[ListSettingsForSnapshotResponse]{ + More: func(ListSettingsForSnapshotResponse) bool { + return ssRespPager.More() + }, + Fetcher: func(ctx context.Context, cur *ListSettingsForSnapshotResponse) (ListSettingsForSnapshotResponse, error) { + page, err := ssRespPager.NextPage(ctx) + if err != nil { + return ListSettingsForSnapshotResponse{}, err + } + + settings := make([]Setting, len(page.Items)) + + for i := 0; i < len(page.Items); i++ { + setting := page.Items[i] + + settings[i] = settingFromGenerated(setting) + } + + return ListSettingsForSnapshotResponse{ + Settings: settings, + SyncToken: SyncToken(*page.SyncToken), + }, nil + }, + Tracer: c.appConfigClient.Tracer(), + }) +} + +// BeginCreateSnapshot creates a snapshot of the configuration store. +// +// - snapshotName - The name of the snapshot to create. +// - keyLabelFilter - The filters to apply on the key-values. +// - options - CreateSnapshotOptions contains the optional parameters to create a Snapshot +func (c *Client) BeginCreateSnapshot(ctx context.Context, snapshotName string, keyLabelFilter []SettingFilter, options *CreateSnapshotOptions) (*runtime.Poller[CreateSnapshotResponse], error) { + filter := []generated.KeyValueFilter{} + + if options == nil { + options = &CreateSnapshotOptions{} + } + + for _, f := range keyLabelFilter { + filter = append(filter, generated.KeyValueFilter{ + Key: f.KeyFilter, + Label: f.LabelFilter, + }) + } + + if len(filter) == 0 { + filter = append(filter, generated.KeyValueFilter{}) + } + + entity := generated.Snapshot{ + Filters: filter, + CompositionType: options.CompositionType, + RetentionPeriod: options.RetentionPeriod, + Tags: options.Tags, + Name: &snapshotName, + } + + opts := generated.AzureAppConfigurationClientBeginCreateSnapshotOptions{ + ResumeToken: options.ResumeToken, + } + + pollerSS, err := generated.NewCreateSnapshotPoller[CreateSnapshotResponse](ctx, c.appConfigClient, snapshotName, entity, &opts) + + if err != nil { + return nil, err + } + + return pollerSS, nil +} + +// GetSnapshot gets a snapshot +// +// - snapshotName - The name of the snapshot to get. +// - options - GetSnapshotOptions contains the optional parameters to get a snapshot +func (c *Client) GetSnapshot(ctx context.Context, snapshotName string, options *GetSnapshotOptions) (GetSnapshotResponse, error) { + if options == nil { + options = &GetSnapshotOptions{} + } + + opts := (*generated.AzureAppConfigurationClientGetSnapshotOptions)(options) + + getResp, err := c.appConfigClient.GetSnapshot(ctx, snapshotName, opts) + + if err != nil { + return GetSnapshotResponse{}, err + } + + convertedETag := azcore.ETag(*getResp.Etag) + + var convertedFilters []KeyValueFilter + + for _, filter := range getResp.Filters { + convertedFilters = append(convertedFilters, KeyValueFilter{ + Key: filter.Key, + Label: filter.Label, + }) + } + + resp := GetSnapshotResponse{ + Snapshot: Snapshot{ + Filters: convertedFilters, + CompositionType: getResp.CompositionType, + RetentionPeriod: getResp.RetentionPeriod, + Tags: getResp.Tags, + Created: getResp.Created, + ETag: &convertedETag, + Expires: getResp.Expires, + ItemsCount: getResp.ItemsCount, + Name: getResp.Snapshot.Name, + Size: getResp.Size, + Status: getResp.Snapshot.Status, + }, + SyncToken: SyncToken(*getResp.SyncToken), + Link: getResp.Link, + } + + return resp, nil +} + +// ArchiveSnapshot archives a snapshot +// +// - snapshotName - The name of the snapshot to archive. +// - options - ArchiveSnapshotOptions contains the optional parameters to archive a snapshot +func (c *Client) ArchiveSnapshot(ctx context.Context, snapshotName string, options *ArchiveSnapshotOptions) (ArchiveSnapshotResponse, error) { + if options == nil { + options = &ArchiveSnapshotOptions{} + } + + opts := updateSnapshotStatusOptions{ + IfMatch: options.IfMatch, + IfNoneMatch: options.IfNoneMatch, + } + resp, err := c.updateSnapshotStatus(ctx, snapshotName, generated.SnapshotStatusArchived, &opts) + + if err != nil { + return ArchiveSnapshotResponse{}, err + } + + return (ArchiveSnapshotResponse)(resp), nil +} + +// RecoverSnapshot recovers a snapshot +// +// - snapshotName - The name of the snapshot to recover. +// - options - RecoverSnapshotOptions contains the optional parameters to recover a snapshot +func (c *Client) RecoverSnapshot(ctx context.Context, snapshotName string, options *RecoverSnapshotOptions) (RecoverSnapshotResponse, error) { + if options == nil { + options = &RecoverSnapshotOptions{} + } + + opts := updateSnapshotStatusOptions{ + IfMatch: options.IfMatch, + IfNoneMatch: options.IfNoneMatch, + } + resp, err := c.updateSnapshotStatus(ctx, snapshotName, generated.SnapshotStatusReady, &opts) + + if err != nil { + return RecoverSnapshotResponse{}, err + } + + return (RecoverSnapshotResponse)(resp), nil +} + +func (c *Client) updateSnapshotStatus(ctx context.Context, snapshotName string, status SnapshotStatus, options *updateSnapshotStatusOptions) (updateSnapshotStatusResponse, error) { + entity := generated.SnapshotUpdateParameters{ + Status: &status, + } + + opts := (*generated.AzureAppConfigurationClientUpdateSnapshotOptions)(options) + + updateResp, err := c.appConfigClient.UpdateSnapshot(ctx, snapshotName, entity, opts) + + if err != nil { + return updateSnapshotStatusResponse{}, err + } + + convertedETag := azcore.ETag(*updateResp.Etag) + + var convertedFilters []KeyValueFilter + + for _, filter := range updateResp.Filters { + convertedFilters = append(convertedFilters, KeyValueFilter{ + Key: filter.Key, + Label: filter.Label, + }) + } + + resp := updateSnapshotStatusResponse{ + Snapshot: Snapshot{ + Filters: convertedFilters, + CompositionType: updateResp.CompositionType, + RetentionPeriod: updateResp.RetentionPeriod, + Tags: updateResp.Tags, + Created: updateResp.Created, + ETag: &convertedETag, + Expires: updateResp.Expires, + ItemsCount: updateResp.ItemsCount, + Name: updateResp.Snapshot.Name, + Size: updateResp.Size, + Status: updateResp.Snapshot.Status, + }, + SyncToken: SyncToken(*updateResp.SyncToken), + Link: updateResp.Link, + } + + return resp, nil +} diff --git a/sdk/data/azappconfig/client_test.go b/sdk/data/azappconfig/client_test.go index f0b6af4579..4835051a82 100644 --- a/sdk/data/azappconfig/client_test.go +++ b/sdk/data/azappconfig/client_test.go @@ -8,13 +8,32 @@ package azappconfig_test import ( "context" + "fmt" + "strings" "testing" + "time" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig" "github.com/stretchr/testify/require" ) +// testId will be used for local testing as a unique identifier. The test proxy will record the base +// request for snapshots. The deletion time for a snapshot is minimum 1 hour. For quicker +// local iteration we will use a unique suffix for each test run. +// to use: switch the testId being used +// +// Snapshot Name: `// + string(testId)` +// KeyValue Prefix: `/*testId +*/` + +// Record Mode +var testId = "120823uid" + +// // Local Testing Mode +// var currTime = time.Now().Unix() +// var testId = strconv.FormatInt(currTime, 10)[len(strconv.FormatInt(currTime, 10))-6:] + func TestClient(t *testing.T) { const ( key = "key-TestClient" @@ -327,3 +346,315 @@ func TestSettingWithEscaping(t *testing.T) { require.NotNil(t, resp.Key) require.EqualValues(t, key, *resp.Key) } + +func TestSnapshotListConfigurationSettings(t *testing.T) { + snapshotName := "listConfigurationsSnapshotTest" + string(testId) + client := NewClientFromConnectionString(t) + + type VL struct { + Value string + Label string + } + + Settings := []azappconfig.Setting{ + { + Value: to.Ptr("value3"), + Label: to.Ptr("label"), + }, + { + Value: to.Ptr("Val1"), + Label: to.Ptr("Label1"), + }, + { + Label: to.Ptr("Label1"), + }, + { + Value: to.Ptr("Val1"), + }, + { + Label: to.Ptr("Label2"), + }, + {}, + } + + Keys := []string{ + "Key", + "Key1", + "Key2", + "KeyNoLabel", + "KeyNoVal", + "NoValNoLabelKey", + } + + require.Equal(t, len(Settings), len(Keys)) + + for i, key := range Keys { + Settings[i].Key = to.Ptr(testId + key) + } + + settingMap := make(map[string][]VL) + + for _, setting := range Settings { + + key := *setting.Key + value := setting.Value + label := setting.Label + + // Add setting to Map + mapV := VL{} + + if value != nil { + mapV.Value = *value + } + + if label != nil { + mapV.Label = *label + } + + settingMap[key] = append(settingMap[key], mapV) + + _, err := client.AddSetting(context.Background(), key, value, nil) + + require.NoError(t, err) + } + + keyFilter := fmt.Sprintf(testId + "*") + sf := []azappconfig.SettingFilter{ + { + KeyFilter: &keyFilter, + }, + } + + _, err := CreateSnapshot(client, snapshotName, sf) + require.NoError(t, err) + + respPgr := client.NewListSettingsForSnapshotPager(snapshotName, nil) + require.NotEmpty(t, respPgr) + + settingsAdded := 0 + + for respPgr.More() { + page, err := respPgr.NextPage(context.Background()) + + require.NoError(t, err) + require.NotEmpty(t, page) + + for _, setting := range page.Settings { + require.NotNil(t, setting.Key) + found := false + + // Check if setting is in the map + for _, configuration := range settingMap[*setting.Key] { + if setting.Value != nil { + if *setting.Value != configuration.Value { + continue + } + } + + if setting.Label != nil { + if *setting.Label != configuration.Label { + continue + } + } + + found = true + settingsAdded++ + break + } + + // Check that the key follows the filtering pattern + if !found { + require.True(t, strings.HasPrefix(*setting.Key, keyFilter[:len(keyFilter)-1])) + } + } + } + + require.Equal(t, len(settingMap), settingsAdded) + + // Cleanup Settings + for _, setting := range Settings { + _, _ = client.DeleteSetting(context.Background(), *setting.Key, nil) + } + + // Cleanup Snapshots + _ = CleanupSnapshot(client, snapshotName) +} + +func TestGetSnapshots(t *testing.T) { + snapshotName := "getSnapshotsTest" + string(testId) + + const ( + ssCreateCount = 5 + ) + + client := NewClientFromConnectionString(t) + + for i := 0; i < ssCreateCount; i++ { + createSSName := snapshotName + fmt.Sprintf("%d", i) + + _, err := client.GetSnapshot(context.Background(), createSSName, nil) + + if err != nil { + _, err = CreateSnapshot(client, createSSName, nil) + require.NoError(t, err) + } + } + + // Get Snapshots + ssPgr := client.NewListSnapshotsPager(nil) + + require.NotEmpty(t, ssPgr) + + snapshotCount := 0 + + for ssPgr.More() { + page, err := ssPgr.NextPage(context.Background()) + + require.NoError(t, err) + require.NotEmpty(t, page) + + for _, snapshot := range page.Snapshots { + if strings.HasPrefix(*snapshot.Name, snapshotName) { + snapshotCount++ + } + } + } + + require.Equal(t, ssCreateCount, snapshotCount) + + // Cleanup Snapshots + for i := 0; i < ssCreateCount; i++ { + cleanSSName := snapshotName + fmt.Sprintf("%d", i) + _ = CleanupSnapshot(client, cleanSSName) + } +} + +func TestSnapshotArchive(t *testing.T) { + snapshotName := "archiveSnapshotsTest" + string(testId) + + client := NewClientFromConnectionString(t) + + snapshot, err := CreateSnapshot(client, snapshotName, nil) + require.NoError(t, err) + + // Snapshot must exist + _, err = client.GetSnapshot(context.Background(), snapshotName, nil) + require.NoError(t, err) + require.Equal(t, azappconfig.SnapshotStatusReady, *snapshot.Status) + + // Archive the snapshot + archiveSnapshot, err := client.ArchiveSnapshot(context.Background(), snapshotName, nil) + require.NoError(t, err) + require.Equal(t, azappconfig.SnapshotStatusArchived, *archiveSnapshot.Snapshot.Status) + + //Best effort snapshot cleanup + _ = CleanupSnapshot(client, snapshotName) +} + +func TestSnapshotRecover(t *testing.T) { + snapshotName := "recoverSnapshotsTest" + string(testId) + + client := NewClientFromConnectionString(t) + + snapshot, err := CreateSnapshot(client, snapshotName, nil) + require.NoError(t, err) + + _, err = client.GetSnapshot(context.Background(), snapshotName, nil) + require.NoError(t, err) + + _, err = client.ArchiveSnapshot(context.Background(), snapshotName, nil) + require.NoError(t, err) + + // Check that snapshot is archived + archivedSnapshot, err := client.GetSnapshot(context.Background(), *snapshot.Name, nil) + require.NoError(t, err) + require.Equal(t, azappconfig.SnapshotStatusArchived, *archivedSnapshot.Snapshot.Status) + + // Recover the snapshot + readySnapshot, err := client.RecoverSnapshot(context.Background(), *snapshot.Name, nil) + require.NoError(t, err) + require.Equal(t, azappconfig.SnapshotStatusReady, *readySnapshot.Snapshot.Status) + + // Best effort snapshot cleanup + _ = CleanupSnapshot(client, snapshotName) +} + +func TestSnapshotCreate(t *testing.T) { + snapshotName := "createSnapshotsTest" + string(testId) + + client := NewClientFromConnectionString(t) + + //Create a snapshot + snapshot, err := CreateSnapshot(client, snapshotName, nil) + + require.NoError(t, err) + require.Equal(t, snapshotName, *snapshot.Name) + + // Best effort cleanup snapshot + _ = CleanupSnapshot(client, snapshotName) +} + +func CreateSnapshot(c *azappconfig.Client, snapshotName string, sf []azappconfig.SettingFilter) (azappconfig.CreateSnapshotResponse, error) { + if sf == nil { + all := "*" + sf = []azappconfig.SettingFilter{ + { + KeyFilter: &all, + }, + } + } + + retPer := int64(3600) + + opts := &azappconfig.CreateSnapshotOptions{ + RetentionPeriod: &retPer, + } + + //Create a snapshot + resp, err := c.BeginCreateSnapshot(context.Background(), snapshotName, sf, opts) + + if err != nil { + return azappconfig.CreateSnapshotResponse{}, err + } + + if resp == nil { + return azappconfig.CreateSnapshotResponse{}, fmt.Errorf("resp is nil") + } + snapshot, err := resp.PollUntilDone(context.Background(), &runtime.PollUntilDoneOptions{ + Frequency: 1 * time.Second, + }) + + if err != nil { + return azappconfig.CreateSnapshotResponse{}, err + } + + //Check if snapshot exists. If not fail the test + _, err = c.GetSnapshot(context.Background(), snapshotName, nil) + + if err != nil { + return azappconfig.CreateSnapshotResponse{}, err + } + + if snapshotName != *snapshot.Name { + return azappconfig.CreateSnapshotResponse{}, fmt.Errorf("Snapshot name does not match") + } + + return snapshot, nil +} + +func CleanupSnapshot(client *azappconfig.Client, snapshotName string) error { + _, err := client.ArchiveSnapshot(context.Background(), snapshotName, nil) + + if err != nil { + return err + } + + //Check if snapshot exists + snapshot, err := client.GetSnapshot(context.Background(), snapshotName, nil) + + if err != nil || *snapshot.Status != azappconfig.SnapshotStatusArchived { + return fmt.Errorf("Snapshot still exists") + } + + return nil +} diff --git a/sdk/data/azappconfig/constants.go b/sdk/data/azappconfig/constants.go index e5c06c8241..1186bde4c7 100644 --- a/sdk/data/azappconfig/constants.go +++ b/sdk/data/azappconfig/constants.go @@ -6,7 +6,9 @@ package azappconfig -import "github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig/internal/generated" +import ( + "github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig/internal/generated" +) // SettingFields are fields to retrieve from a configuration setting. type SettingFields = generated.SettingFields @@ -36,3 +38,69 @@ const ( // A list of tags that can help identify what a configuration setting may be applicable for. SettingFieldsTags SettingFields = generated.SettingFieldsTags ) + +// SnapshotFields are fields to retrieve from a snapshot. +type SnapshotFields = generated.SnapshotFields + +const ( + //The composition type of a snapshot. + SnapshotFieldsCompositionType SnapshotFields = generated.SnapshotFieldsCompositionType + + // The time when the snapshot was created. + SnapshotFieldsCreated SnapshotFields = generated.SnapshotFieldsCreated + + // An ETag indicating the version of a snapshot. + SnapshotFieldsEtag SnapshotFields = generated.SnapshotFieldsEtag + + // The time when the snapshot will expire once archived. + SnapshotFieldsExpires SnapshotFields = generated.SnapshotFieldsExpires + + // A list of filters used to generate the snapshot. + SnapshotFieldsFilters SnapshotFields = generated.SnapshotFieldsFilters + + // The number of items in the snapshot. + SnapshotFieldsItemsCount SnapshotFields = generated.SnapshotFieldsItemsCount + + // The primary identifier of a snapshot. + SnapshotFieldsName SnapshotFields = generated.SnapshotFieldsName + + // Retention period in seconds of the snapshot upon archiving. + SnapshotFieldsRetentionPeriod SnapshotFields = generated.SnapshotFieldsRetentionPeriod + + // Size of the snapshot. + SnapshotFieldsSize SnapshotFields = generated.SnapshotFieldsSize + + // Status of the snapshot. + SnapshotFieldsStatus SnapshotFields = generated.SnapshotFieldsStatus + + // A list of tags on the snapshot. + SnapshotFieldsTags SnapshotFields = generated.SnapshotFieldsTags +) + +// SnapshotStatus contains the current status of the snapshot +type SnapshotStatus = generated.SnapshotStatus + +const ( + // Snapshot is archived state. + SnapshotStatusArchived SnapshotStatus = generated.SnapshotStatusArchived + + // Snapshot is in failing state. + SnapshotStatusFailed SnapshotStatus = generated.SnapshotStatusFailed + + // Snapshot is in provisioning state. + SnapshotStatusProvisioning SnapshotStatus = generated.SnapshotStatusProvisioning + + // Snapshot is in ready state. + SnapshotStatusReady SnapshotStatus = generated.SnapshotStatusReady +) + +// CompositionType is the composition of filters used to create a snapshot. +type CompositionType = generated.CompositionType + +const ( + // Snapshot is composed with a Key filter + CompositionTypeKey CompositionType = generated.CompositionTypeKey + + // Snapshot is composed with a Key and Label filter + CompositionTypeKeyLabel CompositionType = generated.CompositionTypeKeyLabel +) diff --git a/sdk/data/azappconfig/examples_test.go b/sdk/data/azappconfig/examples_test.go index 0fa5f2a8d5..97ab354a83 100644 --- a/sdk/data/azappconfig/examples_test.go +++ b/sdk/data/azappconfig/examples_test.go @@ -242,3 +242,166 @@ func ExampleClient_DeleteSetting() { // Output: } + +func ExampleClient_BeginCreateSnapshot() { + connectionString := os.Getenv("APPCONFIGURATION_CONNECTION_STRING") + if connectionString == "" { + return + } + + client, err := azappconfig.NewClientFromConnectionString(connectionString, nil) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } + + snapshotName := "example-snapshot" + + filter := []azappconfig.SettingFilter{ + { + // TODO: Update the following line with your application specific filter logic + KeyFilter: to.Ptr("*"), + LabelFilter: to.Ptr("*"), + }, + } + + _, err = client.BeginCreateSnapshot(context.TODO(), snapshotName, filter, &azappconfig.CreateSnapshotOptions{}) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } +} + +func ExampleClient_ArchiveSnapshot() { + connectionString := os.Getenv("APPCONFIGURATION_CONNECTION_STRING") + if connectionString == "" { + return + } + + client, err := azappconfig.NewClientFromConnectionString(connectionString, nil) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } + + snapshotName := "existing-snapshot-example" + + _, err = client.ArchiveSnapshot(context.TODO(), snapshotName, &azappconfig.ArchiveSnapshotOptions{}) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } +} + +func ExampleClient_RecoverSnapshot() { + connectionString := os.Getenv("APPCONFIGURATION_CONNECTION_STRING") + if connectionString == "" { + return + } + + client, err := azappconfig.NewClientFromConnectionString(connectionString, nil) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } + + snapshotName := "existing-snapshot-example" + + _, err = client.RecoverSnapshot(context.TODO(), snapshotName, &azappconfig.RecoverSnapshotOptions{}) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } +} + +func ExampleClient_NewListSnapshotsPager() { + connectionString := os.Getenv("APPCONFIGURATION_CONNECTION_STRING") + if connectionString == "" { + return + } + + client, err := azappconfig.NewClientFromConnectionString(connectionString, nil) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } + + snapshotPager := client.NewListSnapshotsPager(nil) + + for snapshotPager.More() { + snapshotPage, err := snapshotPager.NextPage(context.TODO()) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } + + for _, snapshot := range snapshotPage.Snapshots { + // TODO: implement your application specific logic here + _ = snapshot + } + } +} + +func ExampleClient_NewListSettingsForSnapshotPager() { + connectionString := os.Getenv("APPCONFIGURATION_CONNECTION_STRING") + if connectionString == "" { + return + } + + client, err := azappconfig.NewClientFromConnectionString(connectionString, nil) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } + + snapshotName := "existing-snapshot-example" + + snapshotPager := client.NewListSettingsForSnapshotPager(snapshotName, nil) + + for snapshotPager.More() { + snapshotPage, err := snapshotPager.NextPage(context.TODO()) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } + + for _, setting := range snapshotPage.Settings { + // TODO: implement your application specific logic here + _ = setting + } + } +} + +func ExampleClient_GetSnapshot() { + connectionString := os.Getenv("APPCONFIGURATION_CONNECTION_STRING") + if connectionString == "" { + return + } + + client, err := azappconfig.NewClientFromConnectionString(connectionString, nil) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } + + snapshotName := "snapshot-example" + + snapshot, err := client.GetSnapshot(context.TODO(), snapshotName, &azappconfig.GetSnapshotOptions{}) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + } + + _ = snapshot // TODO: do something with snapshot +} diff --git a/sdk/data/azappconfig/go.mod b/sdk/data/azappconfig/go.mod index 635239ff1c..c355c12d5d 100644 --- a/sdk/data/azappconfig/go.mod +++ b/sdk/data/azappconfig/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 - github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.0 + github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 github.com/stretchr/testify v1.8.4 ) diff --git a/sdk/data/azappconfig/go.sum b/sdk/data/azappconfig/go.sum index 18fab75787..e58339a398 100644 --- a/sdk/data/azappconfig/go.sum +++ b/sdk/data/azappconfig/go.sum @@ -2,8 +2,8 @@ github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0 h1:fb8kj/Dh4CSwgsOzHeZY4Xh68 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0/go.mod h1:uReU2sSxZExRPBAg3qKzmAucSi51+SP1OhohieR821Q= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 h1:BMAjVKJM0U/CYF27gA0ZMmXGkOcvfFtD0oHVZ1TIPRI= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0/go.mod h1:1fXstnBMas5kzG+S3q8UoJcmyU6nUeunJcMDHcRYHhs= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.0 h1:d81/ng9rET2YqdVkVwkb6EXeRrLJIwyGnJcAlAWKwhs= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.0/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 h1:6oNBlSdi1QqM1PNW7FPA6xOGA5UNsXnkaYZz9vdPGhA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI= github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 h1:WpB/QDNLpMw72xHJc34BNNykqSOeEJDAWkhf0u12/Jk= github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= diff --git a/sdk/data/azappconfig/internal/generated/custom_client.go b/sdk/data/azappconfig/internal/generated/custom_client.go index abcd16df33..a656785200 100644 --- a/sdk/data/azappconfig/internal/generated/custom_client.go +++ b/sdk/data/azappconfig/internal/generated/custom_client.go @@ -7,7 +7,10 @@ package generated import ( + "context" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" "github.com/Azure/azure-sdk-for-go/sdk/azcore/tracing" ) @@ -21,3 +24,16 @@ func NewAzureAppConfigurationClient(endpoint string, client *azcore.Client) *Azu func (a *AzureAppConfigurationClient) Tracer() tracing.Tracer { return a.internal.Tracer() } + +func NewCreateSnapshotPoller[T any](ctx context.Context, client *AzureAppConfigurationClient, name string, entity Snapshot, options *AzureAppConfigurationClientBeginCreateSnapshotOptions) (*runtime.Poller[T], error) { + if options == nil || options.ResumeToken == "" { + resp, err := client.createSnapshot(ctx, name, entity, options) + if err != nil { + return nil, err + } + poller, err := runtime.NewPoller[T](resp, client.internal.Pipeline(), nil) + return poller, err + } else { + return runtime.NewPollerFromResumeToken[T](options.ResumeToken, client.internal.Pipeline(), nil) + } +} diff --git a/sdk/data/azappconfig/keyvaluefilter.go b/sdk/data/azappconfig/keyvaluefilter.go new file mode 100644 index 0000000000..f32bbf7f91 --- /dev/null +++ b/sdk/data/azappconfig/keyvaluefilter.go @@ -0,0 +1,16 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +package azappconfig + +// KeyValueFilter contains filters to retrieve key-values from a configuration store. +type KeyValueFilter struct { + // REQUIRED; Filters key-values by their key field. + Key *string + + // Filters key-values by their label field. + Label *string +} diff --git a/sdk/data/azappconfig/options.go b/sdk/data/azappconfig/options.go index 2c1214f843..ecc42dbc20 100644 --- a/sdk/data/azappconfig/options.go +++ b/sdk/data/azappconfig/options.go @@ -76,3 +76,109 @@ type SetSettingOptions struct { // if the passed-in ETag is the same version as the one in the configuration store. OnlyIfUnchanged *azcore.ETag } + +// CreateSnapshotOptions contains the optional parameters for the BeginCreateSnapshot method. +type CreateSnapshotOptions struct { + // Resumes the LRO from the provided token. + ResumeToken string + + // The composition type describes how the key-values within the snapshot are composed. The 'key' composition type ensures + // there are no two key-values containing the same key. The 'key_label' composition + // type ensures there are no two key-values containing the same key and label. + CompositionType *CompositionType + + // The amount of time, in seconds, that a snapshot will remain in the archived state before expiring. This property is only + // writable during the creation of a snapshot. If not specified, the default + // lifetime of key-value revisions will be used. + RetentionPeriod *int64 + + // The tags of the snapshot. + Tags map[string]*string +} + +// ArchiveSnapshotOptions contains the optional parameters for the ArchiveSnapshot method. +type ArchiveSnapshotOptions struct { + // Used to perform an operation only if the targeted resource's etag matches the value provided. + IfMatch *string + + // Used to perform an operation only if the targeted resource's etag does not match the value provided. + IfNoneMatch *string +} + +// RestoreSnapshotOptions contains the optional parameters for the RestoreSnapshot method. +type RestoreSnapshotOptions struct { + // Used to perform an operation only if the targeted resource's etag matches the value provided. + IfMatch *string + + // Used to perform an operation only if the targeted resource's etag does not match the value provided. + IfNoneMatch *string +} + +// ListSnapshotsOptions contains the optional parameters for the ListSnapshotsPager method. +type ListSnapshotsOptions struct { + // Instructs the server to return elements that appear after the element referred to by the specified token. + After *string + + // A filter for the name of the returned snapshots. + Name *string + + // Used to select what fields are present in the returned resource(s). + Select []SnapshotFields + + // Used to filter returned snapshots by their status property. + Status []SnapshotStatus +} + +// ListSettingsForSnapshotOptions contains the optional parameters for the NewListSettingsForSnapshotPager method. +type ListSettingsForSnapshotOptions struct { + // Requests the server to respond with the state of the resource at the specified time. + AcceptDatetime *string + + // Instructs the server to return elements that appear after the element referred to by the specified token. + After *string + + // Used to perform an operation only if the targeted resource's etag matches the value provided. + IfMatch *string + + // Used to perform an operation only if the targeted resource's etag does not match the value provided. + IfNoneMatch *string + + // Used to select what fields are present in the returned resource(s). + Select []SettingFields + + // A filter used to match Keys + Key string + + // A filter used to match Labels + Label string +} + +// GetSnapshotOptions contains the optional parameters for the GetSnapshot method. +type GetSnapshotOptions struct { + // Used to perform an operation only if the targeted resource's etag matches the value provided. + IfMatch *string + + // Used to perform an operation only if the targeted resource's etag does not match the value provided. + IfNoneMatch *string + + // Used to select what fields are present in the returned resource(s). + Select []SnapshotFields +} + +// RecoverSnapshotOptions contains the optional parameters for the RecoverSnapshot method. +type RecoverSnapshotOptions struct { + // Used to perform an operation only if the targeted resource's etag matches the value provided. + IfMatch *string + + // Used to perform an operation only if the targeted resource's etag does not match the value provided. + IfNoneMatch *string +} + +// UpdateSnapshotStatusOptions contains the optional parameters for the UpdateSnapshotStatus method. +type updateSnapshotStatusOptions struct { + // Used to perform an operation only if the targeted resource's etag matches the value provided. + IfMatch *string + + // Used to perform an operation only if the targeted resource's etag does not match the value provided. + IfNoneMatch *string +} diff --git a/sdk/data/azappconfig/response_types.go b/sdk/data/azappconfig/response_types.go index 081164a1b7..c2f484d2f2 100644 --- a/sdk/data/azappconfig/response_types.go +++ b/sdk/data/azappconfig/response_types.go @@ -9,6 +9,7 @@ package azappconfig import ( "time" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig/internal/exported" ) @@ -78,3 +79,86 @@ type SetSettingResponse struct { // SyncToken contains the value returned in the Sync-Token header. SyncToken SyncToken } + +// ArchiveSnapshotResponse contains the response from the ArchiveSnapshot method. +type ArchiveSnapshotResponse struct { + Snapshot + + // Link contains the information returned from the Link header response. + Link *string + + // SyncToken contains the information returned from the Sync-Token header response. + SyncToken SyncToken +} + +// ListSnapshotsResponse contains the response from the NewGetSnapshotsPager method. +type ListSnapshotsResponse struct { + // Contains the configuration settings returned that match the setting selector provided. + Snapshots []Snapshot + + // SyncToken contains the value returned in the Sync-Token header. + SyncToken SyncToken +} + +// CreateSnapshotResponse contains the response from the BeginCreateSnapshot method. +type CreateSnapshotResponse struct { + // Read-Only information about the snapshot retrieved from a Create Snapshot operation. + SnapshotInfo +} + +// ListSettingsForSnapshotResponse contains the response from the ListConfigurationSettingsForSnapshot method. +type ListSettingsForSnapshotResponse struct { + // Contains the configuration settings returned that match the setting selector provided. + Settings []Setting + + // SyncToken contains the value returned in the Sync-Token header. + SyncToken SyncToken +} + +// GetSnapshotResponse contains the response from the GetSnapshot method. +type GetSnapshotResponse struct { + // Snapshot object in GetSnapshot Response + Snapshot + + // Link contains the information returned from the Link header response. + Link *string + + // SyncToken contains the information returned from the Sync-Token header response. + SyncToken SyncToken +} + +// RecoverSnapshotResponse contains the response from the RecoverSnapshot method. +type RecoverSnapshotResponse struct { + Snapshot + + // Link contains the information returned from the Link header response. + Link *string + + // SyncToken contains the information returned from the Sync-Token header response. + SyncToken SyncToken +} + +// updateSnapshotStatusResponse contains the response from the UpdateSnapshotStatus method. +type updateSnapshotStatusResponse struct { + Snapshot + + // Link contains the information returned from the Link header response. + Link *string + + // SyncToken contains the information returned from the Sync-Token header response. + SyncToken SyncToken +} + +// AzureAppConfigurationClientUpdateSnapshotResponse contains the response from method AzureAppConfigurationClient.UpdateSnapshot. +type AzureAppConfigurationClientUpdateSnapshotResponse struct { + Snapshot + + // ETag contains the information returned from the ETag header response. + ETag azcore.ETag + + // Link contains the information returned from the Link header response. + Link *string + + // SyncToken contains the information returned from the Sync-Token header response. + SyncToken *string +} diff --git a/sdk/data/azappconfig/setting_selector.go b/sdk/data/azappconfig/setting_selector.go index 1b7f90d3cf..ef1d71ab89 100644 --- a/sdk/data/azappconfig/setting_selector.go +++ b/sdk/data/azappconfig/setting_selector.go @@ -12,6 +12,15 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig/internal/generated" ) +// SettingFilter to select configuration setting entities. +type SettingFilter struct { + // Key filter that will be used to select a set of configuration setting entities. + KeyFilter *string + + // Label filter that will be used to select a set of configuration setting entities. + LabelFilter *string +} + // SettingSelector is a set of options that allows selecting a filtered set of configuration setting entities // from the configuration store, and optionally allows indicating which fields of each setting to retrieve. type SettingSelector struct { diff --git a/sdk/data/azappconfig/snapshot.go b/sdk/data/azappconfig/snapshot.go new file mode 100644 index 0000000000..e56e1a1329 --- /dev/null +++ b/sdk/data/azappconfig/snapshot.go @@ -0,0 +1,82 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +package azappconfig + +import ( + "time" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" +) + +// SnapshotInfo contains the snapshot information returned from a Create Snapshot Request +type SnapshotInfo struct { + + // READ-ONLY; The name of the snapshot. + Name *string `json:"name"` + + // READ-ONLY; The current status of the snapshot. + Status *SnapshotStatus `json:"status"` + + // READ-ONLY; The time that the snapshot was created. + Created *time.Time `json:"created"` + + // READ-ONLY; A value representing the current state of the snapshot. + ETag *azcore.ETag `json:"etag"` + + // READ-ONLY; The time that the snapshot will expire. + Expires *time.Time `json:"expires"` + + // READ-ONLY; The amount of key-values in the snapshot. + ItemsCount *int64 `json:"items_count"` + + // READ-ONLY; The size in bytes of the snapshot. + Size *int64 `json:"size"` + + // READ-ONLY; The retention period of the snapshot on archive in seconds. + RetentionPeriod *int64 `json:"retention_period"` +} + +// Snapshot contains the snapshot information returned from a Get Snapshot Request +type Snapshot struct { + + // REQUIRED; A list of filters used to filter the key-values included in the snapshot. + Filters []KeyValueFilter `json:"filters"` + + // The composition type describes how the key-values within the snapshot are composed. The 'key' composition type ensures + // there are no two key-values containing the same key. The 'key_label' composition + // type ensures there are no two key-values containing the same key and label. + CompositionType *CompositionType `json:"composition_type,omitempty"` + + // The amount of time, in seconds, that a snapshot will remain in the archived state before expiring. This property is only + // writable during the creation of a snapshot. If not specified, the default + // lifetime of key-value revisions will be used. + RetentionPeriod *int64 `json:"retention_period"` + + // The tags of the snapshot. + Tags map[string]*string `json:"tags,omitempty"` + + // READ-ONLY; The time that the snapshot was created. + Created *time.Time `json:"created"` + + // READ-ONLY; A value representing the current state of the snapshot. + ETag *azcore.ETag `json:"etag"` + + // READ-ONLY; The time that the snapshot will expire. + Expires *time.Time `json:"expires,omitempty"` + + // READ-ONLY; The amount of key-values in the snapshot. + ItemsCount *int64 `json:"items_count"` + + // READ-ONLY; The name of the snapshot. + Name *string `json:"name"` + + // READ-ONLY; The size in bytes of the snapshot. + Size *int64 `json:"size"` + + // READ-ONLY; The current status of the snapshot. + Status *SnapshotStatus `json:"status"` +} diff --git a/sdk/data/azappconfig/utils_test.go b/sdk/data/azappconfig/utils_test.go index ae4f07b53f..421d52430a 100644 --- a/sdk/data/azappconfig/utils_test.go +++ b/sdk/data/azappconfig/utils_test.go @@ -11,6 +11,7 @@ import ( "testing" "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig" "github.com/Azure/azure-sdk-for-go/sdk/internal/recording" "github.com/stretchr/testify/require" @@ -54,6 +55,10 @@ func run(m *testing.M) int { if err := recording.AddHeaderRegexSanitizer("x-ms-content-sha256", "fake-content", "", nil); err != nil { panic(err) } + + if err := recording.AddHeaderRegexSanitizer("Operation-Location", "https://contoso.azconfig.io", `https://\w+\.azconfig\.io`, nil); err != nil { + panic(err) + } } return m.Run() @@ -79,6 +84,9 @@ func NewClientFromConnectionString(t *testing.T) *azappconfig.Client { client, err := azappconfig.NewClientFromConnectionString(connStr, &azappconfig.ClientOptions{ ClientOptions: azcore.ClientOptions{ Transport: transport, + Logging: policy.LogOptions{ + IncludeBody: true, + }, }, }) require.NoError(t, err) diff --git a/sdk/data/azappconfig/version.go b/sdk/data/azappconfig/version.go index 3b23673c50..bfcd80f8e1 100644 --- a/sdk/data/azappconfig/version.go +++ b/sdk/data/azappconfig/version.go @@ -8,5 +8,5 @@ package azappconfig const ( moduleName = "github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig" - moduleVersion = "v1.0.1" + moduleVersion = "v1.1.0" )