1
0
Fork 0
mirror of https://github.com/external-secrets/external-secrets.git synced 2024-12-14 11:57:59 +00:00

feat: add wait for values to be created and updated on 1Password side (#3238)

This commit is contained in:
Gergely Brautigam 2024-03-18 11:18:56 +01:00 committed by GitHub
parent 47c0f6c759
commit e589572caf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 99 additions and 18 deletions

View file

@ -123,7 +123,17 @@ func (mockClient *OnePasswordMockClient) GetItemsByTitle(itemUUID, vaultUUID str
// CreateItem will call a validation function if set.
func (mockClient *OnePasswordMockClient) CreateItem(i *onepassword.Item, s string) (*onepassword.Item, error) {
if mockClient.CreateItemValidateFunc != nil {
return mockClient.CreateItemValidateFunc(i, s)
item, err := mockClient.CreateItemValidateFunc(i, s)
if err == nil {
mockClient.MockItems[i.Vault.ID] = append(mockClient.MockItems[i.Vault.ID], *item)
if mockClient.MockItemFields[i.Vault.ID] == nil {
mockClient.MockItemFields[i.Vault.ID] = make(map[string][]*onepassword.ItemField)
}
mockClient.MockItemFields[i.Vault.ID][i.ID] = item.Fields
}
return item, err
}
return &onepassword.Item{}, nil
}
@ -131,7 +141,19 @@ func (mockClient *OnePasswordMockClient) CreateItem(i *onepassword.Item, s strin
// UpdateItem will call a validation function if set.
func (mockClient *OnePasswordMockClient) UpdateItem(i *onepassword.Item, s string) (*onepassword.Item, error) {
if mockClient.UpdateItemValidateFunc != nil {
return mockClient.UpdateItemValidateFunc(i, s)
updatedItem, err := mockClient.UpdateItemValidateFunc(i, s)
if err == nil {
for index, item := range mockClient.MockItems[i.Vault.ID] {
if item.ID == updatedItem.ID {
mockClient.MockItems[i.Vault.ID][index] = *updatedItem
mockClient.MockItemFields[i.Vault.ID][updatedItem.ID] = updatedItem.Fields
break
}
}
}
return updatedItem, err
}
return &onepassword.Item{}, nil
}

View file

@ -20,6 +20,7 @@ import (
"fmt"
"net/url"
"sort"
"time"
"github.com/1Password/connect-sdk-go/connect"
"github.com/1Password/connect-sdk-go/onepassword"
@ -45,8 +46,6 @@ const (
errOnePasswordStoreAtLeastOneVault = "must be at least one vault: spec.provider.onepassword.vaults"
errOnePasswordStoreInvalidConnectHost = "unable to parse URL: spec.provider.onepassword.connectHost: %w"
errOnePasswordStoreNonUniqueVaultNumbers = "vault order numbers must be unique"
errFetchK8sSecret = "could not fetch ConnectToken Secret: %w"
errMissingToken = "missing Secret Token"
errGetVault = "error finding 1Password Vault: %w"
errGetItem = "error finding 1Password Item: %w"
@ -59,7 +58,6 @@ const (
// custom error messages.
errKeyNotFoundMsg = "key not found in 1Password Vaults"
errNoVaultsMsg = "no vaults found"
errNoChangesMsg = "no changes made to 1Password Item"
errExpectedOneItemMsg = "expected one 1Password Item matching"
errExpectedOneFieldMsg = "expected one 1Password ItemField matching"
errExpectedOneFieldMsgF = "%w: '%s' in '%s', got %d"
@ -86,8 +84,10 @@ type ProviderOnePassword struct {
}
// https://github.com/external-secrets/external-secrets/issues/644
var _ esv1beta1.SecretsClient = &ProviderOnePassword{}
var _ esv1beta1.Provider = &ProviderOnePassword{}
var (
_ esv1beta1.SecretsClient = &ProviderOnePassword{}
_ esv1beta1.Provider = &ProviderOnePassword{}
)
// Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
func (provider *ProviderOnePassword) Capabilities() esv1beta1.SecretStoreCapabilities {
@ -251,9 +251,9 @@ func (provider *ProviderOnePassword) createItem(val []byte, ref esv1beta1.PushSe
// the value is different, the value is updated. If the label exists and the
// value is the same, nothing is done.
func updateFieldValue(fields []*onepassword.ItemField, label, newVal string) ([]*onepassword.ItemField, error) {
// This will always iterate over all items
// but its done to ensure that two fields with the same label
// exist resulting in undefined behavior
// This will always iterate over all items.
// This is done to ensure that two fields with the same label
// exist resulting in undefined behavior.
var (
found bool
index int
@ -270,9 +270,9 @@ func updateFieldValue(fields []*onepassword.ItemField, label, newVal string) ([]
if !found {
return append(fields, generateNewItemField(label, newVal)), nil
}
if field := fields[index]; newVal != field.Value {
field.Value = newVal
fields[index] = field
if fields[index].Value != newVal {
fields[index].Value = newVal
}
return fields, nil
@ -289,7 +289,7 @@ func generateNewItemField(label, newVal string) *onepassword.ItemField {
return field
}
func (provider *ProviderOnePassword) PushSecret(_ context.Context, secret *corev1.Secret, ref esv1beta1.PushSecretData) error {
func (provider *ProviderOnePassword) PushSecret(ctx context.Context, secret *corev1.Secret, ref esv1beta1.PushSecretData) error {
val, ok := secret.Data[ref.GetSecretKey()]
if !ok {
return ErrKeyNotFound
@ -301,7 +301,9 @@ func (provider *ProviderOnePassword) PushSecret(_ context.Context, secret *corev
if err = provider.createItem(val, ref); err != nil {
return fmt.Errorf(errCreateItem, err)
}
return nil
err = provider.waitForFunc(ctx, provider.waitForItemToExist(title))
return err
} else if err != nil {
return err
}
@ -319,6 +321,11 @@ func (provider *ProviderOnePassword) PushSecret(_ context.Context, secret *corev
if _, err = provider.client.UpdateItem(providerItem, providerItem.Vault.ID); err != nil {
return fmt.Errorf(errUpdateItem, err)
}
if err := provider.waitForFunc(ctx, provider.waitForLabelToBeUpdated(title, label, val)); err != nil {
return fmt.Errorf("failed waiting for label update: %w", err)
}
return nil
}
@ -580,6 +587,57 @@ func (provider *ProviderOnePassword) getAllForVault(vaultID string, ref esv1beta
return nil
}
// waitForFunc will wait for OnePassword to _actually_ create/update the secret. OnePassword returns immediately after
// the initial create/update which makes the next call for the same item create/update a new item with the same name. Hence, we'll
// wait for the item to exist or be updated on OnePassword's side as well.
// Ideally we could do bulk operations and handle data with one submit, but that would require re-writing the entire
// push secret controller. For now, this is sufficient.
func (provider *ProviderOnePassword) waitForFunc(ctx context.Context, fn func() error) error {
// check every .5 seconds
tick := time.NewTicker(500 * time.Millisecond)
defer tick.Stop()
done, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
var err error
for {
select {
case <-tick.C:
if err = fn(); err == nil {
return nil
}
case <-done.Done():
return fmt.Errorf("timeout to wait for function to run successfully; last error was: %w", err)
}
}
}
func (provider *ProviderOnePassword) waitForItemToExist(title string) func() error {
return func() error {
_, err := provider.findItem(title)
return err
}
}
func (provider *ProviderOnePassword) waitForLabelToBeUpdated(title, label string, val []byte) func() error {
return func() error {
item, err := provider.findItem(title)
if err != nil {
return err
}
for _, field := range item.Fields {
// we found the label with the right value
if field.Label == label && field.Value == string(val) {
return nil
}
}
return fmt.Errorf("label %s no found on value with title %s", title, label)
}
}
func countFieldsWithLabel(fieldLabel string, fields []*onepassword.ItemField) int {
count := 0
for _, field := range fields {

View file

@ -2047,7 +2047,7 @@ func TestProviderOnePasswordPushSecret(t *testing.T) {
},
},
updateValidateFunc: func(item *onepassword.Item, s string) (*onepassword.Item, error) {
validateItem(t, &onepassword.Item{
expectedItem := &onepassword.Item{
Vault: onepassword.ItemVault{
ID: vaultName,
},
@ -2063,8 +2063,9 @@ func TestProviderOnePasswordPushSecret(t *testing.T) {
Type: onepassword.FieldTypeConcealed,
},
},
}, item)
return nil, nil
}
validateItem(t, expectedItem, item)
return expectedItem, nil
},
existingItems: []onepassword.Item{
{