Skip to content

Commit fdf20f5

Browse files
committed
feat: download binaries for cred helpers
Signed-off-by: Donnie Adams <[email protected]>
1 parent 657256e commit fdf20f5

File tree

9 files changed

+74
-63
lines changed

9 files changed

+74
-63
lines changed

pkg/cli/credential.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func (c *Credential) Run(cmd *cobra.Command, _ []string) error {
5858
opts.Runner.RuntimeManager = runtimes.Default(opts.Cache.CacheDir)
5959
}
6060

61-
if err = opts.Runner.RuntimeManager.SetUpCredentialHelpers(cmd.Context(), cfg, opts.Env); err != nil {
61+
if err = opts.Runner.RuntimeManager.SetUpCredentialHelpers(cmd.Context(), cfg); err != nil {
6262
return err
6363
}
6464

pkg/cli/credential_delete.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func (c *Delete) Run(cmd *cobra.Command, args []string) error {
4040
opts.Runner.RuntimeManager = runtimes.Default(opts.Cache.CacheDir)
4141
}
4242

43-
if err = opts.Runner.RuntimeManager.SetUpCredentialHelpers(cmd.Context(), cfg, opts.Env); err != nil {
43+
if err = opts.Runner.RuntimeManager.SetUpCredentialHelpers(cmd.Context(), cfg); err != nil {
4444
return err
4545
}
4646

pkg/cli/credential_show.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func (c *Show) Run(cmd *cobra.Command, args []string) error {
4242
opts.Runner.RuntimeManager = runtimes.Default(opts.Cache.CacheDir)
4343
}
4444

45-
if err = opts.Runner.RuntimeManager.SetUpCredentialHelpers(cmd.Context(), cfg, opts.Env); err != nil {
45+
if err = opts.Runner.RuntimeManager.SetUpCredentialHelpers(cmd.Context(), cfg); err != nil {
4646
return err
4747
}
4848

pkg/credentials/util.go

+1-3
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,13 @@ import (
55
)
66

77
type CredentialHelperDirs struct {
8-
RevisionFile, LastCheckedFile, BinDir, RepoDir, HelperDir string
8+
RevisionFile, LastCheckedFile, BinDir string
99
}
1010

1111
func GetCredentialHelperDirs(cacheDir string) CredentialHelperDirs {
1212
return CredentialHelperDirs{
1313
RevisionFile: filepath.Join(cacheDir, "repos", "gptscript-credential-helpers", "revision"),
1414
LastCheckedFile: filepath.Join(cacheDir, "repos", "gptscript-credential-helpers", "last-checked"),
1515
BinDir: filepath.Join(cacheDir, "repos", "gptscript-credential-helpers", "bin"),
16-
RepoDir: filepath.Join(cacheDir, "repos", "gptscript-credential-helpers", "repo"),
17-
HelperDir: filepath.Join(cacheDir, "repos", "gptscript-credential-helpers"),
1816
}
1917
}

pkg/engine/engine.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ type Model interface {
2121
type RuntimeManager interface {
2222
GetContext(ctx context.Context, tool types.Tool, cmd, env []string) (string, []string, error)
2323
EnsureCredentialHelpers(ctx context.Context) error
24-
SetUpCredentialHelpers(ctx context.Context, cliCfg *config.CLIConfig, env []string) error
24+
SetUpCredentialHelpers(ctx context.Context, cliCfg *config.CLIConfig) error
2525
}
2626

2727
type Engine struct {

pkg/gptscript/gptscript.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ func New(ctx context.Context, o ...Options) (*GPTScript, error) {
9999
opts.Runner.RuntimeManager = runtimes.Default(cacheClient.CacheDir())
100100
}
101101

102-
if err := opts.Runner.RuntimeManager.SetUpCredentialHelpers(context.Background(), cliCfg, opts.Env); err != nil {
102+
if err := opts.Runner.RuntimeManager.SetUpCredentialHelpers(context.Background(), cliCfg); err != nil {
103103
return nil, err
104104
}
105105

pkg/repos/get.go

+34-32
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,16 @@ import (
88
"io/fs"
99
"os"
1010
"path/filepath"
11+
"runtime"
1112
"strings"
1213
"sync"
1314
"time"
1415

1516
"github.com/BurntSushi/locker"
1617
"github.com/gptscript-ai/gptscript/pkg/config"
1718
"github.com/gptscript-ai/gptscript/pkg/credentials"
19+
runtimeEnv "github.com/gptscript-ai/gptscript/pkg/env"
1820
"github.com/gptscript-ai/gptscript/pkg/hash"
19-
"github.com/gptscript-ai/gptscript/pkg/loader/github"
2021
"github.com/gptscript-ai/gptscript/pkg/repos/git"
2122
"github.com/gptscript-ai/gptscript/pkg/repos/runtimes/golang"
2223
"github.com/gptscript-ai/gptscript/pkg/types"
@@ -68,7 +69,6 @@ type credHelperConfig struct {
6869
lock sync.Mutex
6970
initialized bool
7071
cliCfg *config.CLIConfig
71-
env []string
7272
}
7373

7474
func New(cacheDir string, runtimes ...Runtime) *Manager {
@@ -90,7 +90,7 @@ func (m *Manager) EnsureCredentialHelpers(ctx context.Context) error {
9090
defer m.credHelperConfig.lock.Unlock()
9191

9292
if !m.credHelperConfig.initialized {
93-
if err := m.deferredSetUpCredentialHelpers(ctx, m.credHelperConfig.cliCfg, m.credHelperConfig.env); err != nil {
93+
if err := m.deferredSetUpCredentialHelpers(ctx, m.credHelperConfig.cliCfg); err != nil {
9494
return err
9595
}
9696
m.credHelperConfig.initialized = true
@@ -99,27 +99,28 @@ func (m *Manager) EnsureCredentialHelpers(ctx context.Context) error {
9999
return nil
100100
}
101101

102-
func (m *Manager) SetUpCredentialHelpers(_ context.Context, cliCfg *config.CLIConfig, env []string) error {
102+
func (m *Manager) SetUpCredentialHelpers(_ context.Context, cliCfg *config.CLIConfig) error {
103103
m.credHelperConfig = &credHelperConfig{
104104
cliCfg: cliCfg,
105-
env: env,
106105
}
107106
return nil
108107
}
109108

110-
func (m *Manager) deferredSetUpCredentialHelpers(ctx context.Context, cliCfg *config.CLIConfig, env []string) error {
109+
func (m *Manager) deferredSetUpCredentialHelpers(ctx context.Context, cliCfg *config.CLIConfig) error {
111110
var (
112-
helperName = cliCfg.CredentialsStore
113-
suffix string
111+
helperName = cliCfg.CredentialsStore
112+
distInfo, suffix string
114113
)
115-
if helperName == "wincred" {
116-
suffix = ".exe"
117-
}
118-
119114
// The file helper is built-in and does not need to be compiled.
120115
if helperName == "file" {
121116
return nil
122117
}
118+
switch helperName {
119+
case "wincred":
120+
suffix = ".exe"
121+
default:
122+
distInfo = fmt.Sprintf("-%s-%s", runtime.GOOS, runtime.GOARCH)
123+
}
123124

124125
locker.Lock("gptscript-credential-helpers")
125126
defer locker.Unlock("gptscript-credential-helpers")
@@ -137,13 +138,7 @@ func (m *Manager) deferredSetUpCredentialHelpers(ctx context.Context, cliCfg *co
137138
}
138139
}
139140

140-
// Load the credential helpers repo information.
141-
_, _, repo, _, err := github.Load(ctx, nil, credentialHelpersRepo)
142-
if err != nil {
143-
return err
144-
}
145-
146-
if err := os.MkdirAll(m.credHelperDirs.HelperDir, 0755); err != nil {
141+
if err := os.MkdirAll(filepath.Dir(m.credHelperDirs.LastCheckedFile), 0755); err != nil {
147142
return err
148143
}
149144

@@ -152,37 +147,44 @@ func (m *Manager) deferredSetUpCredentialHelpers(ctx context.Context, cliCfg *co
152147
return err
153148
}
154149

155-
var needsBuild bool
150+
tool := types.Tool{
151+
Source: types.ToolSource{
152+
Repo: &types.Repo{
153+
Root: runtimeEnv.VarOrDefault("GPTSCRIPT_CRED_HELPERS_ROOT", "https://github.com/gptscript-ai/gptscript-credential-helpers.git"),
154+
},
155+
},
156+
}
157+
tag, err := golang.GetLatestTag(tool)
158+
if err != nil {
159+
return err
160+
}
156161

162+
var needsDownloaded bool
157163
// Check the last revision shasum and see if it is different from the current one.
158164
lastRevision, err := os.ReadFile(m.credHelperDirs.RevisionFile)
159-
if (err == nil && strings.TrimSpace(string(lastRevision)) != repo.Revision) || errors.Is(err, fs.ErrNotExist) {
165+
if (err == nil && strings.TrimSpace(string(lastRevision)) != tool.Source.Repo.Root+tag) || errors.Is(err, fs.ErrNotExist) {
160166
// Need to pull the latest version.
161-
needsBuild = true
162-
if err := git.Checkout(ctx, m.gitDir, repo.Root, repo.Revision, filepath.Join(m.credHelperDirs.RepoDir, repo.Revision)); err != nil {
163-
return err
164-
}
167+
needsDownloaded = true
165168
// Update the revision file to the new revision.
166-
if err := os.WriteFile(m.credHelperDirs.RevisionFile, []byte(repo.Revision), 0644); err != nil {
169+
if err = os.WriteFile(m.credHelperDirs.RevisionFile, []byte(tool.Source.Repo.Root+tag), 0644); err != nil {
167170
return err
168171
}
169172
} else if err != nil {
170173
return err
171174
}
172175

173-
if !needsBuild {
176+
if !needsDownloaded {
174177
// Check for the existence of the gptscript-credential-osxkeychain binary.
175178
// If it's there, we have no need to build it and can just return.
176-
if _, err := os.Stat(filepath.Join(m.credHelperDirs.BinDir, "gptscript-credential-"+helperName+suffix)); err == nil {
179+
if _, err = os.Stat(filepath.Join(m.credHelperDirs.BinDir, "gptscript-credential-"+helperName+suffix)); err == nil {
177180
return nil
178181
}
179182
}
180183

181184
// Find the Go runtime and use it to build the credential helper.
182-
for _, runtime := range m.runtimes {
183-
if strings.HasPrefix(runtime.ID(), "go") {
184-
goRuntime := runtime.(*golang.Runtime)
185-
return goRuntime.BuildCredentialHelper(ctx, helperName, m.credHelperDirs, m.runtimeDir, repo.Revision, env)
185+
for _, rt := range m.runtimes {
186+
if strings.HasPrefix(rt.ID(), "go") {
187+
return rt.(*golang.Runtime).DownloadCredentialHelper(ctx, tool, helperName, distInfo, suffix, m.credHelperDirs.BinDir)
186188
}
187189
}
188190

pkg/repos/runtimes/golang/golang.go

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

21-
"github.com/gptscript-ai/gptscript/pkg/credentials"
2221
"github.com/gptscript-ai/gptscript/pkg/debugcmd"
2322
runtimeEnv "github.com/gptscript-ai/gptscript/pkg/env"
2423
"github.com/gptscript-ai/gptscript/pkg/hash"
@@ -97,6 +96,14 @@ type tag struct {
9796
} `json:"commit"`
9897
}
9998

99+
func GetLatestTag(tool types.Tool) (string, error) {
100+
r, ok := getLatestRelease(tool)
101+
if !ok {
102+
return "", fmt.Errorf("failed to get latest release for %s", tool.Name)
103+
}
104+
return r.label, nil
105+
}
106+
100107
func getLatestRelease(tool types.Tool) (*release, bool) {
101108
if tool.Source.Repo == nil || !strings.HasPrefix(tool.Source.Repo.Root, "https://github.com/") {
102109
return nil, false
@@ -116,11 +123,14 @@ func getLatestRelease(tool types.Tool) (*release, bool) {
116123
account, repo := parts[1], parts[2]
117124

118125
resp, err := client.Get(fmt.Sprintf("https://api.github.com/repos/%s/%s/tags", account, repo))
119-
if err != nil || resp.StatusCode != http.StatusOK {
126+
if err != nil {
120127
// ignore error
121128
return nil, false
122129
}
123130
defer resp.Body.Close()
131+
if resp.StatusCode != http.StatusOK {
132+
return nil, false
133+
}
124134

125135
var tags []tag
126136
if err := json.NewDecoder(resp.Body).Decode(&tags); err != nil {
@@ -137,11 +147,14 @@ func getLatestRelease(tool types.Tool) (*release, bool) {
137147
}
138148

139149
resp, err = client.Get(fmt.Sprintf("https://github.com/%s/%s/releases/latest", account, repo))
140-
if err != nil || resp.StatusCode != http.StatusFound {
150+
if err != nil {
141151
// ignore error
142152
return nil, false
143153
}
144154
defer resp.Body.Close()
155+
if resp.StatusCode != http.StatusFound {
156+
return nil, false
157+
}
145158

146159
target := resp.Header.Get("Location")
147160
if target == "" {
@@ -212,7 +225,7 @@ func downloadBin(ctx context.Context, checksum, src, url, bin string) error {
212225
return nil
213226
}
214227

215-
func getChecksum(ctx context.Context, rel *release) string {
228+
func getChecksum(ctx context.Context, rel *release, artifactName string) string {
216229
resp, err := get(ctx, rel.checksumTxt())
217230
if err != nil {
218231
// ignore error
@@ -223,7 +236,7 @@ func getChecksum(ctx context.Context, rel *release) string {
223236
scan := bufio.NewScanner(resp.Body)
224237
for scan.Scan() {
225238
fields := strings.Fields(scan.Text())
226-
if len(fields) == 2 && (fields[1] == rel.srcBinName() || fields[1] == "*"+rel.srcBinName()) {
239+
if len(fields) == 2 && (fields[1] == artifactName || fields[1] == "*"+artifactName) {
227240
return fields[0]
228241
}
229242
}
@@ -241,7 +254,7 @@ func (r *Runtime) Binary(ctx context.Context, tool types.Tool, _, toolSource str
241254
return false, nil, nil
242255
}
243256

244-
checksum := getChecksum(ctx, rel)
257+
checksum := getChecksum(ctx, rel, rel.srcBinName())
245258
if checksum == "" {
246259
return false, nil, nil
247260
}
@@ -268,30 +281,28 @@ func (r *Runtime) Setup(ctx context.Context, _ types.Tool, dataRoot, toolSource
268281
return newEnv, nil
269282
}
270283

271-
func (r *Runtime) BuildCredentialHelper(ctx context.Context, helperName string, credHelperDirs credentials.CredentialHelperDirs, dataRoot, revision string, env []string) error {
284+
func (r *Runtime) DownloadCredentialHelper(ctx context.Context, tool types.Tool, helperName, distInfo, suffix string, binDir string) error {
272285
if helperName == "file" {
273286
return nil
274287
}
275288

276-
var suffix string
277-
if helperName == "wincred" {
278-
suffix = ".exe"
289+
rel, ok := getLatestRelease(tool)
290+
if !ok {
291+
return fmt.Errorf("failed to find %s release", r.ID())
292+
}
293+
binaryName := "gptscript-credential-" + helperName
294+
checksum := getChecksum(ctx, rel, binaryName+distInfo+suffix)
295+
if checksum == "" {
296+
return fmt.Errorf("failed to find %s release checksum for os=%s arch=%s", r.ID(), runtime.GOOS, runtime.GOARCH)
279297
}
280298

281-
binPath, err := r.getRuntime(ctx, dataRoot)
282-
if err != nil {
283-
return err
299+
url, _ := strings.CutSuffix(rel.binURL(), rel.srcBinName())
300+
url += binaryName + distInfo + suffix
301+
if err := downloadBin(ctx, checksum, strings.TrimSuffix(binDir, "bin"), url, binaryName+suffix); err != nil {
302+
return fmt.Errorf("failed to download %s release for os=%s arch=%s: %w", r.ID(), runtime.GOOS, runtime.GOARCH, err)
284303
}
285-
newEnv := runtimeEnv.AppendPath(env, binPath)
286304

287-
log.InfofCtx(ctx, "Building credential helper %s", helperName)
288-
cmd := debugcmd.New(ctx, filepath.Join(binPath, "go"),
289-
"build", "-buildvcs=false", "-o",
290-
filepath.Join(credHelperDirs.BinDir, "gptscript-credential-"+helperName+suffix),
291-
fmt.Sprintf("./%s/cmd/", helperName))
292-
cmd.Env = stripGo(append(env, newEnv...))
293-
cmd.Dir = filepath.Join(credHelperDirs.RepoDir, revision)
294-
return cmd.Run()
305+
return nil
295306
}
296307

297308
func (r *Runtime) getReleaseAndDigest() (string, string, error) {

pkg/runner/runtimemanager.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,6 @@ func (r runtimeManagerLogger) EnsureCredentialHelpers(ctx context.Context) error
4545
return r.rm.EnsureCredentialHelpers(mvl.WithInfo(ctx, r))
4646
}
4747

48-
func (r runtimeManagerLogger) SetUpCredentialHelpers(_ context.Context, _ *config.CLIConfig, _ []string) error {
48+
func (r runtimeManagerLogger) SetUpCredentialHelpers(_ context.Context, _ *config.CLIConfig) error {
4949
panic("not implemented")
5050
}

0 commit comments

Comments
 (0)