Skip to content

Commit 61b2bb7

Browse files
authored
environment: Add pass provider (#88)
Support `pass` command line password manager (https://www.passwordstore.org/) as env provider. Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
1 parent 58ab6cd commit 61b2bb7

3 files changed

Lines changed: 70 additions & 4 deletions

File tree

pkg/environment/default.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
package environment
22

33
func NewDefaultProvider() Provider {
4-
return NewMultiProvider(
4+
p := []Provider{
55
NewOsEnvProvider(),
66
NewNoFailProvider(
77
NewOnePasswordProvider(),
88
),
9-
)
9+
}
10+
11+
passProvider, err := NewPassProvider()
12+
if err == nil {
13+
p = append(p, passProvider)
14+
}
15+
16+
return NewMultiProvider(p...)
1017
}

pkg/environment/pass.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package environment
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"errors"
7+
"fmt"
8+
"log/slog"
9+
"os/exec"
10+
"strings"
11+
)
12+
13+
// PassProvider is a provider that retrieves secrets using the `pass` password
14+
// manager.
15+
type PassProvider struct{}
16+
17+
type ErrPassNotAvailable struct{}
18+
19+
func (ErrPassNotAvailable) Error() string {
20+
return "pass is not installed"
21+
}
22+
23+
// NewPassProvider creates a new PassProvider instance.
24+
func NewPassProvider() (*PassProvider, error) {
25+
path, err := exec.LookPath("pass")
26+
if err != nil && !errors.Is(err, exec.ErrNotFound) {
27+
slog.Warn("failed to lookup `pass` binary", "error", err)
28+
}
29+
if path == "" {
30+
return nil, ErrPassNotAvailable{}
31+
}
32+
return &PassProvider{}, nil
33+
}
34+
35+
// Get retrieves the value of a secret by its name using the `pass` CLI.
36+
// The name corresponds to the path in the `pass` store.
37+
func (p *PassProvider) Get(ctx context.Context, name string) (string, error) {
38+
cmd := exec.CommandContext(ctx, "pass", "show", name)
39+
40+
var out bytes.Buffer
41+
var stderr bytes.Buffer
42+
cmd.Stdout = &out
43+
cmd.Stderr = &stderr
44+
45+
err := cmd.Run()
46+
if err != nil {
47+
return "", fmt.Errorf("failed to retrieve secret with `pass`: %w, stderr: %v", err, stderr.String())
48+
}
49+
50+
return strings.TrimSpace(out.String()), nil
51+
}

pkg/teamloader/teamloader.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,15 +157,23 @@ func getModelsForAgent(ctx context.Context, cfg *latest.Config, a *latest.AgentC
157157
return nil, fmt.Errorf("model '%s' not found in configuration", name)
158158
}
159159

160-
env := environment.NewMultiProvider(
160+
providers := []environment.Provider{
161161
environment.NewKeyValueProvider(modelCfg.Env),
162162
environment.NewKeyValueProvider(cfg.Env),
163163
environment.NewEnvFilesProvider(absEnvFiles),
164164
environment.NewOsEnvProvider(), // TODO(dga): Which env should take precedence? OS or config?
165165
environment.NewNoFailProvider(
166166
environment.NewOnePasswordProvider(),
167167
),
168-
)
168+
}
169+
170+
// Append pass provider at the end if available
171+
passProvider, err := environment.NewPassProvider()
172+
if err == nil {
173+
providers = append(providers, passProvider)
174+
}
175+
176+
env := environment.NewMultiProvider(providers...)
169177

170178
model, err := provider.New(ctx, &modelCfg, env, opts...)
171179
if err != nil {

0 commit comments

Comments
 (0)