feat: introduce Source-based config architecture (new_config, new_bo, new_authmgr, new_db, new_profile)#31
Conversation
…db and profile packages
|
|
||
| "github.com/amadeusitgroup/cds/internal/cerr" | ||
| cg "github.com/amadeusitgroup/cds/internal/global" | ||
| nc "github.com/amadeusitgroup/cds/internal/new_bo" |
There was a problem hiding this comment.
What does nc stands for ?
Aliases should be used as little as possible
| source nfg.Source | ||
| ref nfg.SourceRef |
There was a problem hiding this comment.
Why do you need both ?
I would think that we have a generic handler that uses the ref type and gets the appropriate CRUD(er ?) through a factory
| exists, err := s.source.Exists(s.ref.Path) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to check auth file existence: %w", err) | ||
| } | ||
| if !exists { | ||
| s.data = nc.NewAuthStore() | ||
| return nil | ||
| } | ||
|
|
||
| raw, err := s.source.Read(s.ref.Path) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to read auth file at %s: %w", s.ref.Path, err) | ||
| } | ||
|
|
||
| var store nc.AuthStore | ||
| if err := json.Unmarshal(raw, &store); err != nil { | ||
| return fmt.Errorf("failed to parse auth file at %s: %w", s.ref.Path, err) | ||
| } |
There was a problem hiding this comment.
It's even weird to use.
The biggest red flag for me is that the interface Source is from nfg package where in general a package should use interface to "mold" their input effectively constraining the methods instead of the type/fields
source := xxx.getSourceFromRef(s.ref)
if source.Exists () {} // or source.exists not sure what's best I'd guess the method
store := loadStore(source) // essentially json.Unmarshal(source.Read(), &store)There was a problem hiding this comment.
the source could be even stored at New
func NewStore(ref nfg.SourceRef) *Store {
return &Store{
source: xxx.getSourceFromRef(ref),
ref: ref,
data: nc.NewAuthStore(),
}
}There was a problem hiding this comment.
You even created it with ResolveSource after
| type Agent struct { | ||
| Address string `json:"address"` | ||
| CredentialRef string `json:"credentialRef,omitempty"` | ||
| Projects []Project `json:"projects,omitempty"` |
There was a problem hiding this comment.
Hmm not convinced about this, the Agent is only characterized by how the client speaks to it, the project it host should either come from the agent itself reporting through a gRPC call. You may think about caching/storing the association but elsewhere like a project file like we have now saying which agent it used referenced by address I suppose.
| Profile: SourceRef{Type: SourceTypeLocalFS, Path: "/tmp/testcds/.xcds/profile.json"}, | ||
| Recipes: SourceRef{Type: SourceTypeLocalFS, Path: "/tmp/testcds/.xcds/recipes/"}, |
There was a problem hiding this comment.
having the same type for local file and dir is a dangerous game !
| // Returns pointers to the owning host and the project, or nil if not found. | ||
| // Caller must hold m.mu. | ||
| func (m *InventoryManager) findProjectGlobal(projectName string) (*nb.Host, *nb.Project) { | ||
| for i := range m.data.Hosts { |
There was a problem hiding this comment.
| for i := range m.data.Hosts { | |
| for i := range (m.data.Hosts) { |
| if h.Agent == nil { | ||
| continue | ||
| } | ||
| for j := range h.Agent.Projects { |
There was a problem hiding this comment.
| for j := range h.Agent.Projects { | |
| for j := range (h.Agent.Projects) { |
|
|
||
| // Auto-generate ID if empty | ||
| if project.ID == "" { | ||
| project.ID = generateProjectID(project.Name) |
There was a problem hiding this comment.
You also need to check that projectID doesn't already exists
| for i := range m.data.Hosts { | ||
| h := &m.data.Hosts[i] | ||
| if h.Agent == nil { | ||
| continue | ||
| } | ||
| for j := range h.Agent.Projects { | ||
| if h.Agent.Projects[j].Name == projectName { | ||
| h.Agent.Projects = append(h.Agent.Projects[:j], h.Agent.Projects[j+1:]...) | ||
| return nil | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
I just read what this is doing and get the projects from a call would remove this part altogether and moves it on agent side.
| for i := range m.data.Hosts { | |
| h := &m.data.Hosts[i] | |
| if h.Agent == nil { | |
| continue | |
| } | |
| for j := range h.Agent.Projects { | |
| if h.Agent.Projects[j].Name == projectName { | |
| h.Agent.Projects = append(h.Agent.Projects[:j], h.Agent.Projects[j+1:]...) | |
| return nil | |
| } | |
| } | |
| } | |
| for i := range len(m.data.Hosts) { | |
| h := &m.data.Hosts[i] | |
| if h.Agent == nil { | |
| continue | |
| } | |
| for j := range len(h.Agent.Projects) { | |
| if h.Agent.Projects[j].Name == projectName { | |
| h.Agent.Projects = append(h.Agent.Projects[:j], h.Agent.Projects[j+1:]...) | |
| return nil | |
| } | |
| } | |
| } |
There was a problem hiding this comment.
for h := range m.data.Hosts {
slices.DeleteFunc(h.Agent.Projects, func(p project) { return p.name == projectName})
}if agent ==nil I sure hope len(projects) == 0 😆
There was a problem hiding this comment.
This file is to be reviewed the functions are really funky and I don't think storing everything by host is the best Idea because effectively host and agent are /should be extremely close to each other.
The difference is just that Host is the target to spawn the agent, anything beyond needs to be equivalent
|
Closed PR to properly split in smaller PRs so its easier to review and follow |
Summary
Introduces the foundational
new_*packages for the Source-based config architecture refactor. These packages are created alongside the existing ones — no consumers are migrated yet, no existing code is modified.CDS currently hardcodes local filesystem access (
~/.xcds/) across every package. This refactor introduces aSourceinterface as a transport abstraction so config can eventually be backed by localFS, git, S3, CyberArk, etc.What's included
new_config— Source interface & root configSourceinterface (Read,Write,Exists,Delete) withLocalFSSourceimplementationSourceRefstruct (type+path) for inline JSON referencesCdsConfigroot config pointing to 4 sources: inventory, auth, profile, recipesLoadRootConfig(),SaveRootConfig(),DefaultConfig(),LoadOrCreateRootConfig()new_bo— Business objectsHost→Agent→Projecthierarchy (replaces flatdb.jsonmodel)Credentialwith typed variants: SSH, Password, Token, TLSContainer,Inventory,AuthStorevalue typesIDfield (auto-generated asname-<hex>) with globally unique namesnew_authmgr— Credential store & resolverStore: Source-backed credential CRUD with auto-save on mutationsResolver: typed lookups (ResolveSSH,ResolvePassword,ResolveToken,ResolveTLS)PromptPassword/PromptAndSavePasswordfor interactive inputnew_db— Inventory managerInventoryManager: Source-backed Host/Project/Container CRUDAddProjectrequireshostName, all other ops use justprojectNamenew_profile— Profile stubProfileManagerwith Source-backedLoad/SaveProfilestruct with placeholderHolderfield (spec TBD)Key design decisions
afero-based testing — all tests usecos.SetMockedFileSystem()for in-memory FSRelated issues
Type of change
How to test
new_confignew_authmgrnew_dbnew_profileChecklist
make test(or equivalent) and it passed.make lint(if applicable) and it passed.Notes for reviewers
new_*packages are additive only — zero changes to existing code, zero risk to current functionality.new_prefix is temporary. Phase 7 will delete old packages and rename these to their final names.new_profileis intentionally a stub (Profilestruct with a singleHolderfield). The spec will be defined separately.new_*.