88 "strings"
99
1010 "github.com/pgEdge/control-plane/server/internal/database"
11+ "github.com/pgEdge/control-plane/server/internal/filesystem"
1112 "github.com/pgEdge/control-plane/server/internal/resource"
1213)
1314
@@ -23,23 +24,22 @@ func RAGServiceKeysResourceIdentifier(serviceInstanceID string) resource.Identif
2324}
2425
2526// RAGServiceKeysResource manages provider API key files on the host filesystem.
26- // Keys are written to KeysDir and bind-mounted read-only into the RAG container.
27+ // Keys are written to a "keys" subdirectory under the service data directory
28+ // and bind-mounted read-only into the RAG container.
2729// The directory and all files are removed when the service is deleted.
2830type RAGServiceKeysResource struct {
2931 ServiceInstanceID string `json:"service_instance_id"`
3032 HostID string `json:"host_id"`
31- KeysDir string `json:"keys_dir "` // absolute path on host
32- Keys map [string ]string `json:"keys"` // filename → key value
33+ ParentID string `json:"parent_id "` // DirResource ID for the service data directory
34+ Keys map [string ]string `json:"keys"` // filename → key value
3335}
3436
3537func (r * RAGServiceKeysResource ) ResourceVersion () string {
3638 return "1"
3739}
3840
3941func (r * RAGServiceKeysResource ) DiffIgnore () []string {
40- return []string {
41- "/keys_dir" ,
42- }
42+ return nil
4343}
4444
4545func (r * RAGServiceKeysResource ) Identifier () resource.Identifier {
@@ -51,19 +51,30 @@ func (r *RAGServiceKeysResource) Executor() resource.Executor {
5151}
5252
5353func (r * RAGServiceKeysResource ) Dependencies () []resource.Identifier {
54- return nil
54+ return []resource.Identifier {
55+ filesystem .DirResourceIdentifier (r .ParentID ),
56+ }
5557}
5658
5759func (r * RAGServiceKeysResource ) TypeDependencies () []resource.Type {
5860 return nil
5961}
6062
63+ func (r * RAGServiceKeysResource ) keysDir (rc * resource.Context ) (string , error ) {
64+ parentPath , err := filesystem .DirResourceFullPath (rc , r .ParentID )
65+ if err != nil {
66+ return "" , fmt .Errorf ("failed to get service data dir path: %w" , err )
67+ }
68+ return filepath .Join (parentPath , "keys" ), nil
69+ }
70+
6171func (r * RAGServiceKeysResource ) Refresh (ctx context.Context , rc * resource.Context ) error {
62- if r .KeysDir == "" {
72+ keysDir , err := r .keysDir (rc )
73+ if err != nil {
6374 return resource .ErrNotFound
6475 }
6576
66- info , err := os .Stat (r . KeysDir )
77+ info , err := os .Stat (keysDir )
6778 if os .IsNotExist (err ) {
6879 return resource .ErrNotFound
6980 }
@@ -75,7 +86,7 @@ func (r *RAGServiceKeysResource) Refresh(ctx context.Context, rc *resource.Conte
7586 }
7687
7788 for name := range r .Keys {
78- if _ , err := os .Stat (filepath .Join (r . KeysDir , name )); err != nil {
89+ if _ , err := os .Stat (filepath .Join (keysDir , name )); err != nil {
7990 if os .IsNotExist (err ) {
8091 return resource .ErrNotFound
8192 }
@@ -87,40 +98,77 @@ func (r *RAGServiceKeysResource) Refresh(ctx context.Context, rc *resource.Conte
8798}
8899
89100func (r * RAGServiceKeysResource ) Create (ctx context.Context , rc * resource.Context ) error {
90- if err := os .MkdirAll (r .KeysDir , 0o700 ); err != nil {
101+ keysDir , err := r .keysDir (rc )
102+ if err != nil {
103+ return err
104+ }
105+ if err := os .MkdirAll (keysDir , 0o700 ); err != nil {
91106 return fmt .Errorf ("failed to create keys directory: %w" , err )
92107 }
93- return r .writeKeyFiles ()
108+ return r .writeKeyFiles (keysDir )
94109}
95110
96111func (r * RAGServiceKeysResource ) Update (ctx context.Context , rc * resource.Context ) error {
97- return r .writeKeyFiles ()
112+ keysDir , err := r .keysDir (rc )
113+ if err != nil {
114+ return err
115+ }
116+ if err := r .removeStaleKeyFiles (keysDir ); err != nil {
117+ return err
118+ }
119+ return r .writeKeyFiles (keysDir )
98120}
99121
100122func (r * RAGServiceKeysResource ) Delete (ctx context.Context , rc * resource.Context ) error {
101- if r .KeysDir == "" {
123+ keysDir , err := r .keysDir (rc )
124+ if err != nil {
125+ // Parent dir is gone or unresolvable; nothing to clean up.
102126 return nil
103127 }
104- if err := os .RemoveAll (r . KeysDir ); err != nil {
128+ if err := os .RemoveAll (keysDir ); err != nil {
105129 return fmt .Errorf ("failed to remove keys directory: %w" , err )
106130 }
107131 return nil
108132}
109133
110- func (r * RAGServiceKeysResource ) writeKeyFiles () error {
134+ func (r * RAGServiceKeysResource ) writeKeyFiles (keysDir string ) error {
111135 for name , key := range r .Keys {
112136 if err := validateKeyFilename (name ); err != nil {
113137 return err
114138 }
115- path := filepath .Join (r . KeysDir , name )
139+ path := filepath .Join (keysDir , name )
116140 if err := os .WriteFile (path , []byte (key ), 0o600 ); err != nil {
117141 return fmt .Errorf ("failed to write key file %q: %w" , name , err )
118142 }
119143 }
120144 return nil
121145}
122146
123- // validateKeyFilename rejects filenames that could escape KeysDir via path traversal.
147+ // removeStaleKeyFiles deletes key files in keysDir that are no longer in r.Keys.
148+ // This handles the case where a pipeline (and its key files) has been removed.
149+ func (r * RAGServiceKeysResource ) removeStaleKeyFiles (keysDir string ) error {
150+ entries , err := os .ReadDir (keysDir )
151+ if os .IsNotExist (err ) {
152+ return nil
153+ }
154+ if err != nil {
155+ return fmt .Errorf ("failed to read keys directory: %w" , err )
156+ }
157+ for _ , entry := range entries {
158+ if entry .IsDir () {
159+ continue
160+ }
161+ if _ , ok := r .Keys [entry .Name ()]; ! ok {
162+ path := filepath .Join (keysDir , entry .Name ())
163+ if err := os .Remove (path ); err != nil && ! os .IsNotExist (err ) {
164+ return fmt .Errorf ("failed to remove stale key file %q: %w" , entry .Name (), err )
165+ }
166+ }
167+ }
168+ return nil
169+ }
170+
171+ // validateKeyFilename rejects filenames that could escape the keys directory via path traversal.
124172func validateKeyFilename (name string ) error {
125173 if filepath .Clean (name ) != name || filepath .IsAbs (name ) || strings .ContainsAny (name , `/\` ) {
126174 return fmt .Errorf ("invalid key filename %q" , name )
0 commit comments