Skip to content

Commit 5c60812

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

File tree

9 files changed

+77
-68
lines changed

9 files changed

+77
-68
lines changed

Diff for: 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

Diff for: 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

Diff for: 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

Diff for: 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
}

Diff for: 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 {

Diff for: 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

Diff for: pkg/repos/get.go

+37-37
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,21 @@ 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"
2324
)
2425

25-
const credentialHelpersRepo = "github.com/gptscript-ai/gptscript-credential-helpers"
26-
2726
type Runtime interface {
2827
ID() string
2928
Supports(tool types.Tool, cmd []string) bool
@@ -68,7 +67,6 @@ type credHelperConfig struct {
6867
lock sync.Mutex
6968
initialized bool
7069
cliCfg *config.CLIConfig
71-
env []string
7270
}
7371

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

9290
if !m.credHelperConfig.initialized {
93-
if err := m.deferredSetUpCredentialHelpers(ctx, m.credHelperConfig.cliCfg, m.credHelperConfig.env); err != nil {
91+
if err := m.deferredSetUpCredentialHelpers(ctx, m.credHelperConfig.cliCfg); err != nil {
9492
return err
9593
}
9694
m.credHelperConfig.initialized = true
@@ -99,27 +97,28 @@ func (m *Manager) EnsureCredentialHelpers(ctx context.Context) error {
9997
return nil
10098
}
10199

102-
func (m *Manager) SetUpCredentialHelpers(_ context.Context, cliCfg *config.CLIConfig, env []string) error {
100+
func (m *Manager) SetUpCredentialHelpers(_ context.Context, cliCfg *config.CLIConfig) error {
103101
m.credHelperConfig = &credHelperConfig{
104102
cliCfg: cliCfg,
105-
env: env,
106103
}
107104
return nil
108105
}
109106

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

124123
locker.Lock("gptscript-credential-helpers")
125124
defer locker.Unlock("gptscript-credential-helpers")
@@ -137,13 +136,7 @@ func (m *Manager) deferredSetUpCredentialHelpers(ctx context.Context, cliCfg *co
137136
}
138137
}
139138

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 {
139+
if err := os.MkdirAll(filepath.Dir(m.credHelperDirs.LastCheckedFile), 0755); err != nil {
147140
return err
148141
}
149142

@@ -152,37 +145,44 @@ func (m *Manager) deferredSetUpCredentialHelpers(ctx context.Context, cliCfg *co
152145
return err
153146
}
154147

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

160+
var needsDownloaded bool
157161
// Check the last revision shasum and see if it is different from the current one.
158162
lastRevision, err := os.ReadFile(m.credHelperDirs.RevisionFile)
159-
if (err == nil && strings.TrimSpace(string(lastRevision)) != repo.Revision) || errors.Is(err, fs.ErrNotExist) {
163+
if (err == nil && strings.TrimSpace(string(lastRevision)) != tool.Source.Repo.Root+tag) || errors.Is(err, fs.ErrNotExist) {
160164
// 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-
}
165+
needsDownloaded = true
165166
// Update the revision file to the new revision.
166-
if err := os.WriteFile(m.credHelperDirs.RevisionFile, []byte(repo.Revision), 0644); err != nil {
167+
if err = os.WriteFile(m.credHelperDirs.RevisionFile, []byte(tool.Source.Repo.Root+tag), 0644); err != nil {
167168
return err
168169
}
169170
} else if err != nil {
170171
return err
171172
}
172173

173-
if !needsBuild {
174-
// Check for the existence of the gptscript-credential-osxkeychain binary.
175-
// 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 {
174+
if !needsDownloaded {
175+
// Check for the existence of the credential helper binary.
176+
// If it's there, we have no need to download it and can just return.
177+
if _, err = os.Stat(filepath.Join(m.credHelperDirs.BinDir, "gptscript-credential-"+helperName+suffix)); err == nil {
177178
return nil
178179
}
179180
}
180181

181182
// 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)
183+
for _, rt := range m.runtimes {
184+
if strings.HasPrefix(rt.ID(), "go") {
185+
return rt.(*golang.Runtime).DownloadCredentialHelper(ctx, tool, helperName, distInfo, suffix, m.credHelperDirs.BinDir)
186186
}
187187
}
188188

Diff for: 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) {

Diff for: 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)