-
Notifications
You must be signed in to change notification settings - Fork 160
Expand file tree
/
Copy pathsessions.go
More file actions
147 lines (126 loc) · 3.61 KB
/
sessions.go
File metadata and controls
147 lines (126 loc) · 3.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package sessions
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"time"
"github.com/databricks/cli/libs/env"
)
const (
stateFileName = "ssh-tunnel-sessions.json"
// Sessions older than this are considered expired and cleaned up automatically.
sessionMaxAge = 24 * time.Hour
)
// Session represents a tracked SSH tunnel session.
type Session struct {
Name string `json:"name"`
Accelerator string `json:"accelerator"`
WorkspaceHost string `json:"workspace_host"`
CreatedAt time.Time `json:"created_at"`
ClusterID string `json:"cluster_id,omitempty"`
}
// SessionStore holds all tracked sessions.
type SessionStore struct {
Sessions []Session `json:"sessions"`
}
func getStateFilePath(ctx context.Context) (string, error) {
homeDir, err := env.UserHomeDir(ctx)
if err != nil {
return "", fmt.Errorf("failed to get home directory: %w", err)
}
return filepath.Join(homeDir, ".databricks", stateFileName), nil
}
// Load reads the session store from disk. Returns an empty store if the file does not exist.
func Load(ctx context.Context) (*SessionStore, error) {
path, err := getStateFilePath(ctx)
if err != nil {
return nil, err
}
data, err := os.ReadFile(path)
if os.IsNotExist(err) {
return &SessionStore{}, nil
}
if err != nil {
return nil, fmt.Errorf("failed to read session state file: %w", err)
}
var store SessionStore
if err := json.Unmarshal(data, &store); err != nil {
return nil, fmt.Errorf("failed to parse session state file: %w", err)
}
return &store, nil
}
// Save writes the session store to disk atomically.
func Save(ctx context.Context, store *SessionStore) error {
path, err := getStateFilePath(ctx)
if err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
return fmt.Errorf("failed to create state directory: %w", err)
}
data, err := json.MarshalIndent(store, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal session state: %w", err)
}
// Atomic write: write to temp file, then rename.
tmpPath := path + ".tmp"
if err := os.WriteFile(tmpPath, data, 0o600); err != nil {
return fmt.Errorf("failed to write session state file: %w", err)
}
if err := os.Rename(tmpPath, path); err != nil {
return fmt.Errorf("failed to rename session state file: %w", err)
}
return nil
}
// Add persists a new session to the store, replacing any existing session with the same name.
func Add(ctx context.Context, s Session) error {
store, err := Load(ctx)
if err != nil {
return err
}
// Replace existing session with the same name.
found := false
for i, existing := range store.Sessions {
if existing.Name == s.Name {
store.Sessions[i] = s
found = true
break
}
}
if !found {
store.Sessions = append(store.Sessions, s)
}
return Save(ctx, store)
}
// Remove deletes a session by name.
func Remove(ctx context.Context, name string) error {
store, err := Load(ctx)
if err != nil {
return err
}
filtered := store.Sessions[:0]
for _, s := range store.Sessions {
if s.Name != name {
filtered = append(filtered, s)
}
}
store.Sessions = filtered
return Save(ctx, store)
}
// FindMatching returns non-expired sessions that match the given workspace host and accelerator.
func FindMatching(ctx context.Context, workspaceHost, accelerator string) ([]Session, error) {
store, err := Load(ctx)
if err != nil {
return nil, err
}
cutoff := time.Now().Add(-sessionMaxAge)
var result []Session
for _, s := range store.Sessions {
if s.WorkspaceHost == workspaceHost && s.Accelerator == accelerator && s.CreatedAt.After(cutoff) {
result = append(result, s)
}
}
return result, nil
}