From 768f40299e99d3cefdc21d1524e9e8eef6829432 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Fri, 13 Nov 2026 03:10:41 -0800 Subject: [PATCH] credential: keep a per-entry file deleted after the IAM load deleted An entry the load read from its own file is already migrated. When that file is removed before the migration runs, the legacy copy must not recreate it. Co-Authored-By: Claude Opus 5.5 --- .../filer_etc/filer_etc_identity.go | 47 ++++++++++++++++--- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/weed/credential/filer_etc/filer_etc_identity.go b/weed/credential/filer_etc/filer_etc_identity.go index 24b594e801..f4702b034c 100644 --- a/weed/credential/filer_etc/filer_etc_identity.go +++ b/weed/credential/filer_etc/filer_etc_identity.go @@ -39,6 +39,17 @@ func (store *FilerEtcStore) LoadConfiguration(ctx context.Context) (*iam_pb.S3Ap } } + fromLegacy := map[any]bool{} + for _, identity := range s3cfg.Identities { + fromLegacy[identity] = true + } + for _, sa := range s3cfg.ServiceAccounts { + fromLegacy[sa] = true + } + for _, g := range s3cfg.Groups { + fromLegacy[g] = true + } + // 2. Load from multi-file structure (high priority, overrides legacy details) if _, err := store.loadFromMultiFile(ctx, s3cfg); err != nil { return s3cfg, err @@ -58,7 +69,23 @@ func (store *FilerEtcStore) LoadConfiguration(ctx context.Context) (*iam_pb.S3Ap // This ensures that all identities (including legacy ones) are written to individual files // and the legacy file is renamed. if foundLegacy { - if err := store.migrateToMultiFile(ctx, content); err != nil { + loaded := map[string]bool{} + for _, identity := range s3cfg.Identities { + if !fromLegacy[identity] { + loaded[IamIdentitiesDirectory+"/"+identity.Name] = true + } + } + for _, sa := range s3cfg.ServiceAccounts { + if !fromLegacy[sa] { + loaded[IamServiceAccountsDirectory+"/"+sa.Id] = true + } + } + for _, g := range s3cfg.Groups { + if !fromLegacy[g] { + loaded[IamGroupsDirectory+"/"+g.Name] = true + } + } + if err := store.migrateToMultiFile(ctx, content, loaded); err != nil { glog.Errorf("Failed to migrate IAM configuration to multi-file layout: %v", err) return s3cfg, nil } @@ -141,7 +168,7 @@ func (store *FilerEtcStore) loadFromMultiFile(ctx context.Context, s3cfg *iam_pb return hasIdentities, nil } -func (store *FilerEtcStore) migrateToMultiFile(ctx context.Context, legacyContent []byte) error { +func (store *FilerEtcStore) migrateToMultiFile(ctx context.Context, legacyContent []byte, loaded map[string]bool) error { current, found, err := store.readInsideFiler(ctx, filer.IamConfigDirectory, IamLegacyIdentityFile) if err != nil { return err @@ -157,10 +184,16 @@ func (store *FilerEtcStore) migrateToMultiFile(ctx context.Context, legacyConten glog.Infof("Migrating IAM configuration to multi-file layout...") - // An existing per-entry file is newer than the legacy copy, and may have - // been rewritten or removed after this load read it: never overwrite it. + // A per-entry file is newer than the legacy copy, and may have been + // rewritten or removed after this load read it: never write it back. + createIfAbsent := func(subDir, name string, v any) error { + if loaded[subDir+"/"+name] { + return nil + } + return store.createIfAbsent(ctx, subDir, name, v) + } for _, identity := range legacy.Identities { - if err := store.createIfAbsent(ctx, IamIdentitiesDirectory, identity.Name, identity); err != nil { + if err := createIfAbsent(IamIdentitiesDirectory, identity.Name, identity); err != nil { return err } } @@ -168,7 +201,7 @@ func (store *FilerEtcStore) migrateToMultiFile(ctx context.Context, legacyConten if err := validateServiceAccountId(sa.Id); err != nil { return err } - if err := store.createIfAbsent(ctx, IamServiceAccountsDirectory, sa.Id, sa); err != nil { + if err := createIfAbsent(IamServiceAccountsDirectory, sa.Id, sa); err != nil { return err } } @@ -177,7 +210,7 @@ func (store *FilerEtcStore) migrateToMultiFile(ctx context.Context, legacyConten if g.Name == "" { return fmt.Errorf("group name is required") } - if err := store.createIfAbsent(ctx, IamGroupsDirectory, g.Name, g); err != nil { + if err := createIfAbsent(IamGroupsDirectory, g.Name, g); err != nil { return err } }