Skip to content
This repository was archived by the owner on Nov 5, 2024. It is now read-only.

Commit ad7199e

Browse files
authored
feat: support postgres (#7)
Signed-off-by: Grant Linville <[email protected]>
1 parent 288c4f2 commit ad7199e

File tree

10 files changed

+190
-88
lines changed

10 files changed

+190
-88
lines changed

.goreleaser.yml

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ snapshot:
44
version_template: '{{ trimprefix .Summary "v" }}'
55

66
builds:
7-
- id: default
7+
- id: sqlite
8+
main: ./sqlite
89
binary: gptscript-credential-sqlite
910
env:
1011
- CGO_ENABLED=0
@@ -19,14 +20,34 @@ builds:
1920
ldflags:
2021
- -s
2122
- -w
23+
- id: postgres
24+
main: ./postgres
25+
binary: gptscript-credential-postgres
26+
env:
27+
- CGO_ENABLED=0
28+
goos:
29+
- linux
30+
- darwin
31+
goarch:
32+
- amd64
33+
- arm64
34+
flags:
35+
- -trimpath
36+
ldflags:
37+
- -s
38+
- -w
2239

2340
archives:
24-
# Disable archives, we just want the binaries
25-
- id: no_archives
41+
- id: sqlite
2642
format: binary
2743
name_template: "gptscript-credential-sqlite-{{ .Os }}-{{ .Arch }}"
2844
builds:
29-
- default
45+
- sqlite
46+
- id: postgres
47+
format: binary
48+
name_template: "gptscript-credential-postgres-{{ .Os }}-{{ .Arch }}"
49+
builds:
50+
- postgres
3051

3152
checksum:
3253
name_template: "checksums.txt"
@@ -43,5 +64,5 @@ changelog:
4364
release:
4465
github:
4566
owner: gptscript-ai
46-
name: gptscript-credential-sqlite
67+
name: gptscript-credential-database
4768
prerelease: auto

Makefile

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
.PHONY: build-sqlite
2+
build-sqlite:
3+
CGO_ENABLED=0 go build -o bin/gptscript-credential-sqlite -tags "${GO_TAGS}" -ldflags "-s -w" ./sqlite
4+
5+
.PHONY: build-postgres
6+
build-postgres:
7+
CGO_ENABLED=0 go build -o bin/gptscript-credential-postgres -tags "${GO_TAGS}" -ldflags "-s -w" ./postgres
8+
19
.PHONY: build
2-
build:
3-
CGO_ENABLED=0 go build -o bin/gptscript-credential-sqlite -tags "${GO_TAGS}" -ldflags "-s -w" .
10+
build: build-sqlite build-postgres

README.md

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
# gptscript-credential-sqlite
1+
# gptscript-credential-database
22

3-
This is a GPTScript [credential helper](https://docs.gptscript.ai/credentials) for SQLite. When the `sqlite` credential store
4-
is configured for GPTScript, it will use this helper to store credentials in a local SQLite file, located in the configuration directory.
5-
By default, all credentials are stored unencrypted.
3+
This is a set of GPTScript [credential helpers](https://docs.gptscript.ai/credentials) for databases.
4+
Currently, SQLite and PostgreSQL are supported.
5+
To use SQLite, set your GPTScript configuration to use `sqlite` as the credential store.
6+
To use PostgreSQL, set your GPTScript configuration to use `postgres` as the credential store.
7+
8+
By default, all credentials are stored **unencrypted**.
69

710
Only macOS and Linux are supported.
811

@@ -44,5 +47,12 @@ resources:
4447
4548
## Environment Variables
4649
47-
- `GPTSCRIPT_SQLITE_FILE` - can be used to override the path to the SQLite file.
50+
All helpers:
4851
- `GPTSCRIPT_ENCRYPTION_CONFIG_FILE` - can be used to override the path to the encryption configuration file.
52+
53+
SQLite:
54+
- `GPTSCRIPT_SQLITE_FILE` - can be used to override the path to the SQLite file.
55+
56+
PostgreSQL:
57+
- `GPTSCRIPT_POSTGRES_DSN` - (required) the DSN (connection string) for the PostgreSQL database.
58+

go.mod

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
github.com/adrg/xdg v0.4.0
77
github.com/docker/docker-credential-helpers v0.8.2
88
github.com/glebarez/sqlite v1.11.0
9+
gorm.io/driver/postgres v1.5.9
910
gorm.io/gorm v1.25.12
1011
k8s.io/apimachinery v0.31.1
1112
k8s.io/apiserver v0.31.1
@@ -38,6 +39,10 @@ require (
3839
github.com/google/uuid v1.6.0 // indirect
3940
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
4041
github.com/inconshreveable/mousetrap v1.1.0 // indirect
42+
github.com/jackc/pgpassfile v1.0.0 // indirect
43+
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
44+
github.com/jackc/pgx/v5 v5.5.5 // indirect
45+
github.com/jackc/puddle/v2 v2.2.1 // indirect
4146
github.com/jinzhu/inflection v1.0.0 // indirect
4247
github.com/jinzhu/now v1.1.5 // indirect
4348
github.com/josharian/intern v1.0.0 // indirect

go.sum

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,14 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1
6969
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
7070
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
7171
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
72+
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
73+
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
74+
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
75+
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
76+
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
77+
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
78+
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
79+
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
7280
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
7381
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
7482
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
@@ -224,6 +232,8 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
224232
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
225233
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
226234
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
235+
gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8=
236+
gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
227237
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
228238
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
229239
k8s.io/api v0.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU=

main.go

Lines changed: 0 additions & 19 deletions
This file was deleted.

pkg/sqlite/encryption.go renamed to pkg/common/encryption.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package sqlite
1+
package common
22

33
import (
44
"context"
@@ -31,13 +31,13 @@ func readEncryptionConfig(ctx context.Context) (*encryptionconfig.EncryptionConf
3131
return encryptionconfig.LoadEncryptionConfig(ctx, encryptionConfigPath, false, "gptscript")
3232
}
3333

34-
func (s Sqlite) encryptCred(ctx context.Context, cred GptscriptCredential) (GptscriptCredential, error) {
35-
if s.transformer == nil {
34+
func (d Database) encryptCred(ctx context.Context, cred GptscriptCredential) (GptscriptCredential, error) {
35+
if d.transformer == nil {
3636
return cred, nil
3737
}
3838

3939
secretBytes := []byte(cred.Secret)
40-
encryptedSecretBytes, err := s.transformer.TransformToStorage(ctx, secretBytes, uid(cred.ServerURL))
40+
encryptedSecretBytes, err := d.transformer.TransformToStorage(ctx, secretBytes, uid(cred.ServerURL))
4141
if err != nil {
4242
return GptscriptCredential{}, fmt.Errorf("failed to encrypt secret: %w", err)
4343
}
@@ -46,8 +46,8 @@ func (s Sqlite) encryptCred(ctx context.Context, cred GptscriptCredential) (Gpts
4646
return cred, nil
4747
}
4848

49-
func (s Sqlite) decryptCred(ctx context.Context, cred GptscriptCredential) (GptscriptCredential, error) {
50-
if s.transformer == nil {
49+
func (d Database) decryptCred(ctx context.Context, cred GptscriptCredential) (GptscriptCredential, error) {
50+
if d.transformer == nil {
5151
return cred, nil
5252
}
5353

@@ -59,7 +59,7 @@ func (s Sqlite) decryptCred(ctx context.Context, cred GptscriptCredential) (Gpts
5959
return GptscriptCredential{}, fmt.Errorf("failed to decode secret: %w", err)
6060
}
6161

62-
secretBytes, _, err := s.transformer.TransformFromStorage(ctx, encryptedSecretBytes, uid(cred.ServerURL))
62+
secretBytes, _, err := d.transformer.TransformFromStorage(ctx, encryptedSecretBytes, uid(cred.ServerURL))
6363
if err != nil {
6464
return GptscriptCredential{}, fmt.Errorf("failed to decrypt secret: %w", err)
6565
}

pkg/sqlite/sqlite.go renamed to pkg/common/sqlite.go

Lines changed: 25 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,13 @@
1-
package sqlite
1+
package common
22

33
import (
44
"context"
55
"errors"
66
"fmt"
7-
"log"
8-
"os"
97
"time"
108

11-
"github.com/adrg/xdg"
129
"github.com/docker/docker-credential-helpers/credentials"
13-
"github.com/glebarez/sqlite"
1410
"gorm.io/gorm"
15-
"gorm.io/gorm/logger"
1611
"k8s.io/apimachinery/pkg/runtime/schema"
1712
"k8s.io/apiserver/pkg/storage/value"
1813
)
@@ -31,53 +26,33 @@ var groupResource = schema.GroupResource{
3126
Resource: "credentials",
3227
}
3328

34-
type Sqlite struct {
29+
type Database struct {
3530
db *gorm.DB
3631
transformer value.Transformer
3732
}
3833

39-
func NewSqlite(ctx context.Context) (Sqlite, error) {
40-
var (
41-
dbPath string
42-
err error
43-
)
44-
if os.Getenv("GPTSCRIPT_SQLITE_FILE") != "" {
45-
dbPath = os.Getenv("GPTSCRIPT_SQLITE_FILE")
46-
} else {
47-
dbPath, err = xdg.ConfigFile("gptscript/credentials.db")
48-
if err != nil {
49-
return Sqlite{}, fmt.Errorf("failed to get credentials db path: %w", err)
50-
}
51-
}
52-
53-
db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{
54-
Logger: logger.New(log.New(os.Stdout, "\r\n", log.LstdFlags), logger.Config{
55-
LogLevel: logger.Error,
56-
IgnoreRecordNotFoundError: true,
57-
}),
58-
})
59-
if err != nil {
60-
return Sqlite{}, fmt.Errorf("failed to open database: %w", err)
61-
}
62-
34+
func NewDatabase(ctx context.Context, db *gorm.DB) (Database, error) {
6335
if err := db.AutoMigrate(&GptscriptCredential{}); err != nil {
64-
return Sqlite{}, fmt.Errorf("failed to auto migrate GptscriptCredential: %w", err)
36+
return Database{}, fmt.Errorf("failed to auto migrate GptscriptCredential: %w", err)
6537
}
6638

67-
s := Sqlite{db: db}
68-
6939
encryptionConf, err := readEncryptionConfig(ctx)
7040
if err != nil {
71-
return Sqlite{}, fmt.Errorf("failed to read encryption config: %w", err)
41+
return Database{}, fmt.Errorf("failed to read encryption config: %w", err)
7242
} else if encryptionConf != nil {
7343
transformer, exists := encryptionConf.Transformers[groupResource]
7444
if !exists {
75-
return Sqlite{}, fmt.Errorf("failed to find encryption transformer for %s", groupResource.String())
45+
return Database{}, fmt.Errorf("failed to find encryption transformer for %s", groupResource.String())
7646
}
77-
s.transformer = transformer
47+
return Database{
48+
db: db,
49+
transformer: transformer,
50+
}, nil
7851
}
7952

80-
return s, nil
53+
return Database{
54+
db: db,
55+
}, nil
8156
}
8257

8358
type GptscriptCredential struct {
@@ -88,14 +63,14 @@ type GptscriptCredential struct {
8863
Secret string
8964
}
9065

91-
func (s Sqlite) Add(creds *credentials.Credentials) error {
66+
func (d Database) Add(creds *credentials.Credentials) error {
9267
cred := GptscriptCredential{
9368
ServerURL: creds.ServerURL,
9469
Username: creds.Username,
9570
Secret: creds.Secret,
9671
}
9772

98-
cred, err := s.encryptCred(context.Background(), cred)
73+
cred, err := d.encryptCred(context.Background(), cred)
9974
if err != nil {
10075
return fmt.Errorf("failed to encrypt credential: %w", err)
10176
}
@@ -104,61 +79,61 @@ func (s Sqlite) Add(creds *credentials.Credentials) error {
10479
// If it does, delete it first.
10580
// This would normally happen during a credential refresh.
10681
var existing GptscriptCredential
107-
if err := s.db.Where("server_url = ?", cred.ServerURL).First(&existing).Error; err != nil {
82+
if err := d.db.Where("server_url = ?", cred.ServerURL).First(&existing).Error; err != nil {
10883
if !errors.Is(err, gorm.ErrRecordNotFound) {
10984
return fmt.Errorf("failed to get existing credential: %w", err)
11085
}
11186
} else {
112-
if err := s.db.Delete(&existing).Error; err != nil {
87+
if err := d.db.Delete(&existing).Error; err != nil {
11388
return fmt.Errorf("failed to delete existing credential: %w", err)
11489
}
11590
}
11691

117-
if err := s.db.Create(&cred).Error; err != nil {
92+
if err := d.db.Create(&cred).Error; err != nil {
11893
return fmt.Errorf("failed to create credential: %w", err)
11994
}
12095

12196
return nil
12297
}
12398

124-
func (s Sqlite) Delete(serverURL string) error {
99+
func (d Database) Delete(serverURL string) error {
125100
var (
126101
cred GptscriptCredential
127102
err error
128103
)
129-
if err = s.db.Where("server_url = ?", serverURL).Delete(&cred).Error; err != nil {
104+
if err = d.db.Where("server_url = ?", serverURL).Delete(&cred).Error; err != nil {
130105
return fmt.Errorf("failed to delete credential: %w", err)
131106
}
132107

133108
return nil
134109
}
135110

136-
func (s Sqlite) Get(serverURL string) (string, string, error) {
111+
func (d Database) Get(serverURL string) (string, string, error) {
137112
var (
138113
cred GptscriptCredential
139114
err error
140115
)
141-
if err = s.db.Where("server_url = ?", serverURL).First(&cred).Error; err != nil {
116+
if err = d.db.Where("server_url = ?", serverURL).First(&cred).Error; err != nil {
142117
if errors.Is(err, gorm.ErrRecordNotFound) {
143118
return "", "", nil
144119
}
145120
return "", "", fmt.Errorf("failed to get credential: %w", err)
146121
}
147122

148-
cred, err = s.decryptCred(context.Background(), cred)
123+
cred, err = d.decryptCred(context.Background(), cred)
149124
if err != nil {
150125
return "", "", fmt.Errorf("failed to decrypt credential: %w", err)
151126
}
152127

153128
return cred.Username, cred.Secret, nil
154129
}
155130

156-
func (s Sqlite) List() (map[string]string, error) {
131+
func (d Database) List() (map[string]string, error) {
157132
var (
158133
creds []GptscriptCredential
159134
err error
160135
)
161-
if err = s.db.Find(&creds).Error; err != nil {
136+
if err = d.db.Find(&creds).Error; err != nil {
162137
if errors.Is(err, gorm.ErrRecordNotFound) {
163138
return nil, nil
164139
}

0 commit comments

Comments
 (0)