Skip to content

Commit fde5920

Browse files
authored
feat: support sqlite credential helper (#857)
Signed-off-by: Grant Linville <[email protected]>
1 parent dd4ba0d commit fde5920

File tree

5 files changed

+93
-40
lines changed

5 files changed

+93
-40
lines changed

pkg/config/cliconfig.go

+31-12
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,32 @@ import (
1515
"github.com/docker/cli/cli/config/types"
1616
)
1717

18+
const (
19+
WincredCredHelper = "wincred"
20+
OsxkeychainCredHelper = "osxkeychain"
21+
SecretserviceCredHelper = "secretservice"
22+
PassCredHelper = "pass"
23+
FileCredHelper = "file"
24+
SqliteCredHelper = "sqlite"
25+
26+
GPTScriptHelperPrefix = "gptscript-credential-"
27+
)
28+
1829
var (
19-
darwinHelpers = []string{"osxkeychain", "file"}
20-
windowsHelpers = []string{"wincred", "file"}
21-
linuxHelpers = []string{"secretservice", "pass", "file"}
30+
darwinHelpers = []string{OsxkeychainCredHelper, FileCredHelper, SqliteCredHelper}
31+
windowsHelpers = []string{WincredCredHelper, FileCredHelper}
32+
linuxHelpers = []string{SecretserviceCredHelper, PassCredHelper, FileCredHelper, SqliteCredHelper}
2233
)
2334

24-
const GPTScriptHelperPrefix = "gptscript-credential-"
35+
func listAsString(helpers []string) string {
36+
if len(helpers) == 0 {
37+
return ""
38+
} else if len(helpers) == 1 {
39+
return helpers[0]
40+
}
41+
42+
return strings.Join(helpers[:len(helpers)-1], ", ") + " or " + helpers[len(helpers)-1]
43+
}
2544

2645
type AuthConfig types.AuthConfig
2746

@@ -150,13 +169,13 @@ func ReadCLIConfig(gptscriptConfigFile string) (*CLIConfig, error) {
150169
errMsg := fmt.Sprintf("invalid credential store '%s'", result.CredentialsStore)
151170
switch runtime.GOOS {
152171
case "darwin":
153-
errMsg += " (use 'osxkeychain' or 'file')"
172+
errMsg += fmt.Sprintf(" (use %s)", listAsString(darwinHelpers))
154173
case "windows":
155-
errMsg += " (use 'wincred' or 'file')"
174+
errMsg += fmt.Sprintf(" (use %s)", listAsString(windowsHelpers))
156175
case "linux":
157-
errMsg += " (use 'secretservice', 'pass', or 'file')"
176+
errMsg += fmt.Sprintf(" (use %s)", listAsString(linuxHelpers))
158177
default:
159-
errMsg += " (use 'file')"
178+
errMsg += " (use file)"
160179
}
161180
errMsg += fmt.Sprintf("\nPlease edit your config file at %s to fix this.", result.location)
162181

@@ -169,11 +188,11 @@ func ReadCLIConfig(gptscriptConfigFile string) (*CLIConfig, error) {
169188
func (c *CLIConfig) setDefaultCredentialsStore() error {
170189
switch runtime.GOOS {
171190
case "darwin":
172-
c.CredentialsStore = "osxkeychain"
191+
c.CredentialsStore = OsxkeychainCredHelper
173192
case "windows":
174-
c.CredentialsStore = "wincred"
193+
c.CredentialsStore = WincredCredHelper
175194
default:
176-
c.CredentialsStore = "file"
195+
c.CredentialsStore = FileCredHelper
177196
}
178197
return c.Save()
179198
}
@@ -187,7 +206,7 @@ func isValidCredentialHelper(helper string) bool {
187206
case "linux":
188207
return slices.Contains(linuxHelpers, helper)
189208
default:
190-
return helper == "file"
209+
return helper == FileCredHelper
191210
}
192211
}
193212

pkg/credentials/store.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func NewStore(cfg *config.CLIConfig, credentialBuilder CredentialBuilder, credCt
4646
return Store{
4747
credCtxs: credCtxs,
4848
credBuilder: credentialBuilder,
49-
credHelperDirs: GetCredentialHelperDirs(cacheDir),
49+
credHelperDirs: GetCredentialHelperDirs(cacheDir, cfg.CredentialsStore),
5050
cfg: cfg,
5151
}, nil
5252
}
@@ -199,7 +199,7 @@ func (s *Store) getStore(ctx context.Context) (credentials.Store, error) {
199199
}
200200

201201
func (s *Store) getStoreByHelper(ctx context.Context, helper string) (credentials.Store, error) {
202-
if helper == "" || helper == config.GPTScriptHelperPrefix+"file" {
202+
if helper == "" || helper == config.GPTScriptHelperPrefix+config.FileCredHelper {
203203
return credentials.NewFileStore(s.cfg), nil
204204
}
205205

pkg/credentials/util.go

+29-4
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,43 @@
11
package credentials
22

33
import (
4+
"fmt"
45
"path/filepath"
6+
7+
"github.com/gptscript-ai/gptscript/pkg/config"
8+
runtimeEnv "github.com/gptscript-ai/gptscript/pkg/env"
59
)
610

711
type CredentialHelperDirs struct {
812
RevisionFile, LastCheckedFile, BinDir string
913
}
1014

11-
func GetCredentialHelperDirs(cacheDir string) CredentialHelperDirs {
15+
func RepoNameForCredentialStore(store string) string {
16+
switch store {
17+
case config.SqliteCredHelper:
18+
return "gptscript-credential-sqlite"
19+
default:
20+
return "gptscript-credential-helpers"
21+
}
22+
}
23+
24+
func GitURLForRepoName(repoName string) (string, error) {
25+
switch repoName {
26+
case "gptscript-credential-sqlite":
27+
return runtimeEnv.VarOrDefault("GPTSCRIPT_CRED_SQLITE_ROOT", "https://github.com/gptscript-ai/gptscript-credential-sqlite.git"), nil
28+
case "gptscript-credential-helpers":
29+
return runtimeEnv.VarOrDefault("GPTSCRIPT_CRED_HELPERS_ROOT", "https://github.com/gptscript-ai/gptscript-credential-helpers.git"), nil
30+
default:
31+
return "", fmt.Errorf("unknown repo name: %s", repoName)
32+
}
33+
}
34+
35+
func GetCredentialHelperDirs(cacheDir, store string) CredentialHelperDirs {
36+
repoName := RepoNameForCredentialStore(store)
1237
return CredentialHelperDirs{
13-
RevisionFile: filepath.Join(cacheDir, "repos", "gptscript-credential-helpers", "revision"),
14-
LastCheckedFile: filepath.Join(cacheDir, "repos", "gptscript-credential-helpers", "last-checked"),
15-
BinDir: filepath.Join(cacheDir, "repos", "gptscript-credential-helpers", "bin"),
38+
RevisionFile: filepath.Join(cacheDir, "repos", repoName, "revision"),
39+
LastCheckedFile: filepath.Join(cacheDir, "repos", repoName, "last-checked"),
40+
BinDir: filepath.Join(cacheDir, "repos", repoName, "bin"),
1641
}
1742
}
1843

pkg/repos/get.go

+29-21
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import (
1616
"github.com/BurntSushi/locker"
1717
"github.com/gptscript-ai/gptscript/pkg/config"
1818
"github.com/gptscript-ai/gptscript/pkg/credentials"
19-
runtimeEnv "github.com/gptscript-ai/gptscript/pkg/env"
2019
"github.com/gptscript-ai/gptscript/pkg/hash"
2120
"github.com/gptscript-ai/gptscript/pkg/repos/git"
2221
"github.com/gptscript-ai/gptscript/pkg/repos/runtimes/golang"
@@ -55,10 +54,10 @@ func (n noopRuntime) Setup(_ context.Context, _ types.Tool, _, _ string, _ []str
5554
}
5655

5756
type Manager struct {
57+
cacheDir string
5858
storageDir string
5959
gitDir string
6060
runtimeDir string
61-
credHelperDirs credentials.CredentialHelperDirs
6261
runtimes []Runtime
6362
credHelperConfig *credHelperConfig
6463
}
@@ -72,11 +71,11 @@ type credHelperConfig struct {
7271
func New(cacheDir string, runtimes ...Runtime) *Manager {
7372
root := filepath.Join(cacheDir, "repos")
7473
return &Manager{
75-
storageDir: root,
76-
gitDir: filepath.Join(root, "git"),
77-
runtimeDir: filepath.Join(root, "runtimes"),
78-
credHelperDirs: credentials.GetCredentialHelperDirs(cacheDir),
79-
runtimes: runtimes,
74+
cacheDir: cacheDir,
75+
storageDir: root,
76+
gitDir: filepath.Join(root, "git"),
77+
runtimeDir: filepath.Join(root, "runtimes"),
78+
runtimes: runtimes,
8079
}
8180
}
8281

@@ -110,50 +109,59 @@ func (m *Manager) deferredSetUpCredentialHelpers(ctx context.Context, cliCfg *co
110109
distInfo, suffix string
111110
)
112111
// The file helper is built-in and does not need to be downloaded.
113-
if helperName == "file" {
112+
if helperName == config.FileCredHelper {
114113
return nil
115114
}
116115
switch helperName {
117-
case "wincred":
116+
case config.WincredCredHelper:
118117
suffix = ".exe"
119118
default:
120119
distInfo = fmt.Sprintf("-%s-%s", runtime.GOOS, runtime.GOARCH)
121120
}
122121

123-
locker.Lock("gptscript-credential-helpers")
124-
defer locker.Unlock("gptscript-credential-helpers")
122+
repoName := credentials.RepoNameForCredentialStore(helperName)
123+
124+
locker.Lock(repoName)
125+
defer locker.Unlock(repoName)
126+
127+
credHelperDirs := credentials.GetCredentialHelperDirs(m.cacheDir, helperName)
125128

126129
// Load the last-checked file to make sure we haven't checked the repo in the last 24 hours.
127130
now := time.Now()
128-
lastChecked, err := os.ReadFile(m.credHelperDirs.LastCheckedFile)
131+
lastChecked, err := os.ReadFile(credHelperDirs.LastCheckedFile)
129132
if err == nil {
130133
if t, err := time.Parse(time.RFC3339, strings.TrimSpace(string(lastChecked))); err == nil && now.Sub(t) < 24*time.Hour {
131134
// Make sure the binary still exists, and if it does, return.
132-
if _, err := os.Stat(filepath.Join(m.credHelperDirs.BinDir, "gptscript-credential-"+helperName+suffix)); err == nil {
135+
if _, err := os.Stat(filepath.Join(credHelperDirs.BinDir, "gptscript-credential-"+helperName+suffix)); err == nil {
133136
log.Debugf("Credential helper %s up-to-date as of %v, checking for updates after %v", helperName, t, t.Add(24*time.Hour))
134137
return nil
135138
}
136139
}
137140
}
138141

139-
if err := os.MkdirAll(filepath.Dir(m.credHelperDirs.LastCheckedFile), 0755); err != nil {
142+
if err := os.MkdirAll(filepath.Dir(credHelperDirs.LastCheckedFile), 0755); err != nil {
140143
return err
141144
}
142145

143146
// Update the last-checked file.
144-
if err := os.WriteFile(m.credHelperDirs.LastCheckedFile, []byte(now.Format(time.RFC3339)), 0644); err != nil {
147+
if err := os.WriteFile(credHelperDirs.LastCheckedFile, []byte(now.Format(time.RFC3339)), 0644); err != nil {
148+
return err
149+
}
150+
151+
gitURL, err := credentials.GitURLForRepoName(repoName)
152+
if err != nil {
145153
return err
146154
}
147155

148156
tool := types.Tool{
149157
ToolDef: types.ToolDef{
150158
Parameters: types.Parameters{
151-
Name: "gptscript-credential-helpers",
159+
Name: repoName,
152160
},
153161
},
154162
Source: types.ToolSource{
155163
Repo: &types.Repo{
156-
Root: runtimeEnv.VarOrDefault("GPTSCRIPT_CRED_HELPERS_ROOT", "https://github.com/gptscript-ai/gptscript-credential-helpers.git"),
164+
Root: gitURL,
157165
},
158166
},
159167
}
@@ -164,12 +172,12 @@ func (m *Manager) deferredSetUpCredentialHelpers(ctx context.Context, cliCfg *co
164172

165173
var needsDownloaded bool
166174
// Check the last revision shasum and see if it is different from the current one.
167-
lastRevision, err := os.ReadFile(m.credHelperDirs.RevisionFile)
175+
lastRevision, err := os.ReadFile(credHelperDirs.RevisionFile)
168176
if (err == nil && strings.TrimSpace(string(lastRevision)) != tool.Source.Repo.Root+tag) || errors.Is(err, fs.ErrNotExist) {
169177
// Need to pull the latest version.
170178
needsDownloaded = true
171179
// Update the revision file to the new revision.
172-
if err = os.WriteFile(m.credHelperDirs.RevisionFile, []byte(tool.Source.Repo.Root+tag), 0644); err != nil {
180+
if err = os.WriteFile(credHelperDirs.RevisionFile, []byte(tool.Source.Repo.Root+tag), 0644); err != nil {
173181
return err
174182
}
175183
} else if err != nil {
@@ -179,15 +187,15 @@ func (m *Manager) deferredSetUpCredentialHelpers(ctx context.Context, cliCfg *co
179187
if !needsDownloaded {
180188
// Check for the existence of the credential helper binary.
181189
// If it's there, we have no need to download it and can just return.
182-
if _, err = os.Stat(filepath.Join(m.credHelperDirs.BinDir, "gptscript-credential-"+helperName+suffix)); err == nil {
190+
if _, err = os.Stat(filepath.Join(credHelperDirs.BinDir, "gptscript-credential-"+helperName+suffix)); err == nil {
183191
return nil
184192
}
185193
}
186194

187195
// Find the Go runtime and use it to build the credential helper.
188196
for _, rt := range m.runtimes {
189197
if strings.HasPrefix(rt.ID(), "go") {
190-
return rt.(*golang.Runtime).DownloadCredentialHelper(ctx, tool, helperName, distInfo, suffix, m.credHelperDirs.BinDir)
198+
return rt.(*golang.Runtime).DownloadCredentialHelper(ctx, tool, helperName, distInfo, suffix, credHelperDirs.BinDir)
191199
}
192200
}
193201

pkg/repos/runtimes/golang/golang.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"runtime"
1919
"strings"
2020

21+
"github.com/gptscript-ai/gptscript/pkg/config"
2122
"github.com/gptscript-ai/gptscript/pkg/debugcmd"
2223
runtimeEnv "github.com/gptscript-ai/gptscript/pkg/env"
2324
"github.com/gptscript-ai/gptscript/pkg/hash"
@@ -286,7 +287,7 @@ func (r *Runtime) Setup(ctx context.Context, _ types.Tool, dataRoot, toolSource
286287
}
287288

288289
func (r *Runtime) DownloadCredentialHelper(ctx context.Context, tool types.Tool, helperName, distInfo, suffix string, binDir string) error {
289-
if helperName == "file" {
290+
if helperName == config.FileCredHelper {
290291
return nil
291292
}
292293

0 commit comments

Comments
 (0)