mirror of
https://github.com/googleapis/genai-toolbox.git
synced 2026-01-20 04:48:02 -05:00
Compare commits
35 Commits
js-sdk-doc
...
spanner-cr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de7c65eb1c | ||
|
|
589059ed3f | ||
|
|
41b518b955 | ||
|
|
6e8a9eb8ec | ||
|
|
ef8f3b02f2 | ||
|
|
351b007fe3 | ||
|
|
9d1feca108 | ||
|
|
17b41f6453 | ||
|
|
a7799757c9 | ||
|
|
d961e373e1 | ||
|
|
bcb40a720d | ||
|
|
4a4cf1e712 | ||
|
|
b706b5bc68 | ||
|
|
4d3332d37d | ||
|
|
1203b7370a | ||
|
|
4a26ce3c1b | ||
|
|
306b5becda | ||
|
|
6d43488ddf | ||
|
|
3a317a3455 | ||
|
|
af62ddf9c1 | ||
|
|
1475b4d092 | ||
|
|
c67b0cf0fc | ||
|
|
511c4651f3 | ||
|
|
62095feadb | ||
|
|
f9f34b1005 | ||
|
|
4170fe309f | ||
|
|
a9edd5e32d | ||
|
|
09c979d8db | ||
|
|
7d79f4909a | ||
|
|
330dd843bf | ||
|
|
e831f71421 | ||
|
|
4f62db499d | ||
|
|
e6de2170cf | ||
|
|
5b7f5a3039 | ||
|
|
302b564ca1 |
@@ -340,6 +340,26 @@ steps:
|
|||||||
spanner \
|
spanner \
|
||||||
spanner || echo "Integration tests failed." # ignore test failures
|
spanner || echo "Integration tests failed." # ignore test failures
|
||||||
|
|
||||||
|
- id: "spanner-admin"
|
||||||
|
name: golang:1
|
||||||
|
waitFor: ["compile-test-binary"]
|
||||||
|
entrypoint: /bin/bash
|
||||||
|
env:
|
||||||
|
- "GOPATH=/gopath"
|
||||||
|
- "SPANNER_PROJECT=$PROJECT_ID"
|
||||||
|
- "SERVICE_ACCOUNT_EMAIL=$SERVICE_ACCOUNT_EMAIL"
|
||||||
|
secretEnv: ["CLIENT_ID"]
|
||||||
|
volumes:
|
||||||
|
- name: "go"
|
||||||
|
path: "/gopath"
|
||||||
|
args:
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
.ci/test_with_coverage.sh \
|
||||||
|
"Spanner Admin" \
|
||||||
|
spanneradmin \
|
||||||
|
spanneradmin || echo "Integration tests failed."
|
||||||
|
|
||||||
- id: "neo4j"
|
- id: "neo4j"
|
||||||
name: golang:1
|
name: golang:1
|
||||||
waitFor: ["compile-test-binary"]
|
waitFor: ["compile-test-binary"]
|
||||||
@@ -825,7 +845,27 @@ steps:
|
|||||||
elasticsearch \
|
elasticsearch \
|
||||||
elasticsearch
|
elasticsearch
|
||||||
|
|
||||||
|
- id: "snowflake"
|
||||||
|
name: golang:1
|
||||||
|
waitFor: ["compile-test-binary"]
|
||||||
|
entrypoint: /bin/bash
|
||||||
|
env:
|
||||||
|
- "GOPATH=/gopath"
|
||||||
|
- "SERVICE_ACCOUNT_EMAIL=$SERVICE_ACCOUNT_EMAIL"
|
||||||
|
- "SNOWFLAKE_DATABASE=$_SNOWFLAKE_DATABASE"
|
||||||
|
- "SNOWFLAKE_SCHEMA=$_SNOWFLAKE_SCHEMA"
|
||||||
|
secretEnv: ["CLIENT_ID", "SNOWFLAKE_USER", "SNOWFLAKE_PASS", "SNOWFLAKE_ACCOUNT"]
|
||||||
|
volumes:
|
||||||
|
- name: "go"
|
||||||
|
path: "/gopath"
|
||||||
|
args:
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
.ci/test_with_coverage.sh \
|
||||||
|
"Snowflake" \
|
||||||
|
snowflake \
|
||||||
|
snowflake
|
||||||
|
|
||||||
- id: "cassandra"
|
- id: "cassandra"
|
||||||
name: golang:1
|
name: golang:1
|
||||||
waitFor: ["compile-test-binary"]
|
waitFor: ["compile-test-binary"]
|
||||||
@@ -1038,6 +1078,12 @@ availableSecrets:
|
|||||||
env: ELASTICSEARCH_USER
|
env: ELASTICSEARCH_USER
|
||||||
- versionName: projects/$PROJECT_ID/secrets/elastic_search_pass/versions/latest
|
- versionName: projects/$PROJECT_ID/secrets/elastic_search_pass/versions/latest
|
||||||
env: ELASTICSEARCH_PASS
|
env: ELASTICSEARCH_PASS
|
||||||
|
- versionName: projects/$PROJECT_ID/secrets/snowflake_account/versions/latest
|
||||||
|
env: SNOWFLAKE_ACCOUNT
|
||||||
|
- versionName: projects/$PROJECT_ID/secrets/snowflake_user/versions/latest
|
||||||
|
env: SNOWFLAKE_USER
|
||||||
|
- versionName: projects/$PROJECT_ID/secrets/snowflake_pass/versions/latest
|
||||||
|
env: SNOWFLAKE_PASS
|
||||||
- versionName: projects/$PROJECT_ID/secrets/cassandra_user/versions/latest
|
- versionName: projects/$PROJECT_ID/secrets/cassandra_user/versions/latest
|
||||||
env: CASSANDRA_USER
|
env: CASSANDRA_USER
|
||||||
- versionName: projects/$PROJECT_ID/secrets/cassandra_pass/versions/latest
|
- versionName: projects/$PROJECT_ID/secrets/cassandra_pass/versions/latest
|
||||||
@@ -1124,4 +1170,5 @@ substitutions:
|
|||||||
_SINGLESTORE_USER: "root"
|
_SINGLESTORE_USER: "root"
|
||||||
_MARIADB_PORT: "3307"
|
_MARIADB_PORT: "3307"
|
||||||
_MARIADB_DATABASE: test_database
|
_MARIADB_DATABASE: test_database
|
||||||
|
_SNOWFLAKE_DATABASE: "test"
|
||||||
|
_SNOWFLAKE_SCHEMA: "PUBLIC"
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -20,4 +20,4 @@ node_modules
|
|||||||
|
|
||||||
# executable
|
# executable
|
||||||
genai-toolbox
|
genai-toolbox
|
||||||
toolbox
|
toolbox
|
||||||
@@ -51,6 +51,10 @@ ignoreFiles = ["quickstart/shared", "quickstart/python", "quickstart/js", "quick
|
|||||||
# Add a new version block here before every release
|
# Add a new version block here before every release
|
||||||
# The order of versions in this file is mirrored into the dropdown
|
# The order of versions in this file is mirrored into the dropdown
|
||||||
|
|
||||||
|
[[params.versions]]
|
||||||
|
version = "v0.25.0"
|
||||||
|
url = "https://googleapis.github.io/genai-toolbox/v0.25.0/"
|
||||||
|
|
||||||
[[params.versions]]
|
[[params.versions]]
|
||||||
version = "v0.24.0"
|
version = "v0.24.0"
|
||||||
url = "https://googleapis.github.io/genai-toolbox/v0.24.0/"
|
url = "https://googleapis.github.io/genai-toolbox/v0.24.0/"
|
||||||
|
|||||||
25
CHANGELOG.md
25
CHANGELOG.md
@@ -1,5 +1,30 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [0.25.0](https://github.com/googleapis/genai-toolbox/compare/v0.24.0...v0.25.0) (2026-01-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Add `embeddingModel` support ([#2121](https://github.com/googleapis/genai-toolbox/issues/2121)) ([9c62f31](https://github.com/googleapis/genai-toolbox/commit/9c62f313ff5edf0a3b5b8a3e996eba078fba4095))
|
||||||
|
* Add `allowed-hosts` flag ([#2254](https://github.com/googleapis/genai-toolbox/issues/2254)) ([17b41f6](https://github.com/googleapis/genai-toolbox/commit/17b41f64531b8fe417c28ada45d1992ba430dc1b))
|
||||||
|
* Add parameter default value to manifest ([#2264](https://github.com/googleapis/genai-toolbox/issues/2264)) ([9d1feca](https://github.com/googleapis/genai-toolbox/commit/9d1feca10810fa42cb4c94a409252f1bd373ee36))
|
||||||
|
* **snowflake:** Add Snowflake Source and Tools ([#858](https://github.com/googleapis/genai-toolbox/issues/858)) ([b706b5b](https://github.com/googleapis/genai-toolbox/commit/b706b5bc685aeda277f277868bae77d38d5fd7b6))
|
||||||
|
* **prebuilt/cloud-sql-mysql:** Update CSQL MySQL prebuilt tools to use IAM ([#2202](https://github.com/googleapis/genai-toolbox/issues/2202)) ([731a32e](https://github.com/googleapis/genai-toolbox/commit/731a32e5360b4d6862d81fcb27d7127c655679a8))
|
||||||
|
* **sources/bigquery:** Make credentials scope configurable ([#2210](https://github.com/googleapis/genai-toolbox/issues/2210)) ([a450600](https://github.com/googleapis/genai-toolbox/commit/a4506009b93771b77fb05ae97044f914967e67ed))
|
||||||
|
* **sources/trino:** Add ssl verification options and fix docs example ([#2155](https://github.com/googleapis/genai-toolbox/issues/2155)) ([4a4cf1e](https://github.com/googleapis/genai-toolbox/commit/4a4cf1e712b671853678dba99c4dc49dd4fc16a2))
|
||||||
|
* **tools/looker:** Add ability to set destination folder with `make_look` and `make_dashboard`. ([#2245](https://github.com/googleapis/genai-toolbox/issues/2245)) ([eb79339](https://github.com/googleapis/genai-toolbox/commit/eb793398cd1cc4006d9808ccda5dc7aea5e92bd5))
|
||||||
|
* **tools/postgressql:** Add tool to list store procedure ([#2156](https://github.com/googleapis/genai-toolbox/issues/2156)) ([cf0fc51](https://github.com/googleapis/genai-toolbox/commit/cf0fc515b57d9b84770076f3c0c5597c4597ef62))
|
||||||
|
* **tools/postgressql:** Add Parameter `embeddedBy` config support ([#2151](https://github.com/googleapis/genai-toolbox/issues/2151)) ([17b70cc](https://github.com/googleapis/genai-toolbox/commit/17b70ccaa754d15bcc33a1a3ecb7e652520fa600))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **server:** Add `embeddingModel` config initialization ([#2281](https://github.com/googleapis/genai-toolbox/issues/2281)) ([a779975](https://github.com/googleapis/genai-toolbox/commit/a7799757c9345f99b6d2717841fbf792d364e1a2))
|
||||||
|
* **sources/cloudgda:** Add import for cloudgda source ([#2217](https://github.com/googleapis/genai-toolbox/issues/2217)) ([7daa411](https://github.com/googleapis/genai-toolbox/commit/7daa4111f4ebfb0a35319fd67a8f7b9f0f99efcf))
|
||||||
|
* **tools/alloydb-wait-for-operation:** Fix connection message generation ([#2228](https://github.com/googleapis/genai-toolbox/issues/2228)) ([7053fbb](https://github.com/googleapis/genai-toolbox/commit/7053fbb1953653143d39a8510916ea97a91022a6))
|
||||||
|
* **tools/alloydbainl:** Only add psv when NL Config Param is defined ([#2265](https://github.com/googleapis/genai-toolbox/issues/2265)) ([ef8f3b0](https://github.com/googleapis/genai-toolbox/commit/ef8f3b02f2f38ce94a6ba9acf35d08b9469bef4e))
|
||||||
|
* **tools/looker:** Looker client OAuth nil pointer error ([#2231](https://github.com/googleapis/genai-toolbox/issues/2231)) ([268700b](https://github.com/googleapis/genai-toolbox/commit/268700bdbf8281de0318d60ca613ed3672990b20))
|
||||||
|
|
||||||
## [0.24.0](https://github.com/googleapis/genai-toolbox/compare/v0.23.0...v0.24.0) (2025-12-19)
|
## [0.24.0](https://github.com/googleapis/genai-toolbox/compare/v0.23.0...v0.24.0) (2025-12-19)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
14
README.md
14
README.md
@@ -140,7 +140,7 @@ To install Toolbox as a binary:
|
|||||||
>
|
>
|
||||||
> ```sh
|
> ```sh
|
||||||
> # see releases page for other versions
|
> # see releases page for other versions
|
||||||
> export VERSION=0.24.0
|
> export VERSION=0.25.0
|
||||||
> curl -L -o toolbox https://storage.googleapis.com/genai-toolbox/v$VERSION/linux/amd64/toolbox
|
> curl -L -o toolbox https://storage.googleapis.com/genai-toolbox/v$VERSION/linux/amd64/toolbox
|
||||||
> chmod +x toolbox
|
> chmod +x toolbox
|
||||||
> ```
|
> ```
|
||||||
@@ -153,7 +153,7 @@ To install Toolbox as a binary:
|
|||||||
>
|
>
|
||||||
> ```sh
|
> ```sh
|
||||||
> # see releases page for other versions
|
> # see releases page for other versions
|
||||||
> export VERSION=0.24.0
|
> export VERSION=0.25.0
|
||||||
> curl -L -o toolbox https://storage.googleapis.com/genai-toolbox/v$VERSION/darwin/arm64/toolbox
|
> curl -L -o toolbox https://storage.googleapis.com/genai-toolbox/v$VERSION/darwin/arm64/toolbox
|
||||||
> chmod +x toolbox
|
> chmod +x toolbox
|
||||||
> ```
|
> ```
|
||||||
@@ -166,7 +166,7 @@ To install Toolbox as a binary:
|
|||||||
>
|
>
|
||||||
> ```sh
|
> ```sh
|
||||||
> # see releases page for other versions
|
> # see releases page for other versions
|
||||||
> export VERSION=0.24.0
|
> export VERSION=0.25.0
|
||||||
> curl -L -o toolbox https://storage.googleapis.com/genai-toolbox/v$VERSION/darwin/amd64/toolbox
|
> curl -L -o toolbox https://storage.googleapis.com/genai-toolbox/v$VERSION/darwin/amd64/toolbox
|
||||||
> chmod +x toolbox
|
> chmod +x toolbox
|
||||||
> ```
|
> ```
|
||||||
@@ -179,7 +179,7 @@ To install Toolbox as a binary:
|
|||||||
>
|
>
|
||||||
> ```cmd
|
> ```cmd
|
||||||
> :: see releases page for other versions
|
> :: see releases page for other versions
|
||||||
> set VERSION=0.24.0
|
> set VERSION=0.25.0
|
||||||
> curl -o toolbox.exe "https://storage.googleapis.com/genai-toolbox/v%VERSION%/windows/amd64/toolbox.exe"
|
> curl -o toolbox.exe "https://storage.googleapis.com/genai-toolbox/v%VERSION%/windows/amd64/toolbox.exe"
|
||||||
> ```
|
> ```
|
||||||
>
|
>
|
||||||
@@ -191,7 +191,7 @@ To install Toolbox as a binary:
|
|||||||
>
|
>
|
||||||
> ```powershell
|
> ```powershell
|
||||||
> # see releases page for other versions
|
> # see releases page for other versions
|
||||||
> $VERSION = "0.24.0"
|
> $VERSION = "0.25.0"
|
||||||
> curl.exe -o toolbox.exe "https://storage.googleapis.com/genai-toolbox/v$VERSION/windows/amd64/toolbox.exe"
|
> curl.exe -o toolbox.exe "https://storage.googleapis.com/genai-toolbox/v$VERSION/windows/amd64/toolbox.exe"
|
||||||
> ```
|
> ```
|
||||||
>
|
>
|
||||||
@@ -204,7 +204,7 @@ You can also install Toolbox as a container:
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
# see releases page for other versions
|
# see releases page for other versions
|
||||||
export VERSION=0.24.0
|
export VERSION=0.25.0
|
||||||
docker pull us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:$VERSION
|
docker pull us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:$VERSION
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -228,7 +228,7 @@ To install from source, ensure you have the latest version of
|
|||||||
[Go installed](https://go.dev/doc/install), and then run the following command:
|
[Go installed](https://go.dev/doc/install), and then run the following command:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
go install github.com/googleapis/genai-toolbox@v0.24.0
|
go install github.com/googleapis/genai-toolbox@v0.25.0
|
||||||
```
|
```
|
||||||
<!-- {x-release-please-end} -->
|
<!-- {x-release-please-end} -->
|
||||||
|
|
||||||
|
|||||||
@@ -215,10 +215,13 @@ import (
|
|||||||
_ "github.com/googleapis/genai-toolbox/internal/tools/serverlessspark/serverlesssparklistbatches"
|
_ "github.com/googleapis/genai-toolbox/internal/tools/serverlessspark/serverlesssparklistbatches"
|
||||||
_ "github.com/googleapis/genai-toolbox/internal/tools/singlestore/singlestoreexecutesql"
|
_ "github.com/googleapis/genai-toolbox/internal/tools/singlestore/singlestoreexecutesql"
|
||||||
_ "github.com/googleapis/genai-toolbox/internal/tools/singlestore/singlestoresql"
|
_ "github.com/googleapis/genai-toolbox/internal/tools/singlestore/singlestoresql"
|
||||||
|
_ "github.com/googleapis/genai-toolbox/internal/tools/snowflake/snowflakeexecutesql"
|
||||||
|
_ "github.com/googleapis/genai-toolbox/internal/tools/snowflake/snowflakesql"
|
||||||
_ "github.com/googleapis/genai-toolbox/internal/tools/spanner/spannerexecutesql"
|
_ "github.com/googleapis/genai-toolbox/internal/tools/spanner/spannerexecutesql"
|
||||||
_ "github.com/googleapis/genai-toolbox/internal/tools/spanner/spannerlistgraphs"
|
_ "github.com/googleapis/genai-toolbox/internal/tools/spanner/spannerlistgraphs"
|
||||||
_ "github.com/googleapis/genai-toolbox/internal/tools/spanner/spannerlisttables"
|
_ "github.com/googleapis/genai-toolbox/internal/tools/spanner/spannerlisttables"
|
||||||
_ "github.com/googleapis/genai-toolbox/internal/tools/spanner/spannersql"
|
_ "github.com/googleapis/genai-toolbox/internal/tools/spanner/spannersql"
|
||||||
|
_ "github.com/googleapis/genai-toolbox/internal/tools/spanneradmin/spannercreateinstance"
|
||||||
_ "github.com/googleapis/genai-toolbox/internal/tools/sqlite/sqliteexecutesql"
|
_ "github.com/googleapis/genai-toolbox/internal/tools/sqlite/sqliteexecutesql"
|
||||||
_ "github.com/googleapis/genai-toolbox/internal/tools/sqlite/sqlitesql"
|
_ "github.com/googleapis/genai-toolbox/internal/tools/sqlite/sqlitesql"
|
||||||
_ "github.com/googleapis/genai-toolbox/internal/tools/tidb/tidbexecutesql"
|
_ "github.com/googleapis/genai-toolbox/internal/tools/tidb/tidbexecutesql"
|
||||||
@@ -263,7 +266,9 @@ import (
|
|||||||
_ "github.com/googleapis/genai-toolbox/internal/sources/redis"
|
_ "github.com/googleapis/genai-toolbox/internal/sources/redis"
|
||||||
_ "github.com/googleapis/genai-toolbox/internal/sources/serverlessspark"
|
_ "github.com/googleapis/genai-toolbox/internal/sources/serverlessspark"
|
||||||
_ "github.com/googleapis/genai-toolbox/internal/sources/singlestore"
|
_ "github.com/googleapis/genai-toolbox/internal/sources/singlestore"
|
||||||
|
_ "github.com/googleapis/genai-toolbox/internal/sources/snowflake"
|
||||||
_ "github.com/googleapis/genai-toolbox/internal/sources/spanner"
|
_ "github.com/googleapis/genai-toolbox/internal/sources/spanner"
|
||||||
|
_ "github.com/googleapis/genai-toolbox/internal/sources/spanneradmin"
|
||||||
_ "github.com/googleapis/genai-toolbox/internal/sources/sqlite"
|
_ "github.com/googleapis/genai-toolbox/internal/sources/sqlite"
|
||||||
_ "github.com/googleapis/genai-toolbox/internal/sources/tidb"
|
_ "github.com/googleapis/genai-toolbox/internal/sources/tidb"
|
||||||
_ "github.com/googleapis/genai-toolbox/internal/sources/trino"
|
_ "github.com/googleapis/genai-toolbox/internal/sources/trino"
|
||||||
@@ -378,7 +383,9 @@ func NewCommand(opts ...Option) *Command {
|
|||||||
flags.BoolVar(&cmd.cfg.Stdio, "stdio", false, "Listens via MCP STDIO instead of acting as a remote HTTP server.")
|
flags.BoolVar(&cmd.cfg.Stdio, "stdio", false, "Listens via MCP STDIO instead of acting as a remote HTTP server.")
|
||||||
flags.BoolVar(&cmd.cfg.DisableReload, "disable-reload", false, "Disables dynamic reloading of tools file.")
|
flags.BoolVar(&cmd.cfg.DisableReload, "disable-reload", false, "Disables dynamic reloading of tools file.")
|
||||||
flags.BoolVar(&cmd.cfg.UI, "ui", false, "Launches the Toolbox UI web server.")
|
flags.BoolVar(&cmd.cfg.UI, "ui", false, "Launches the Toolbox UI web server.")
|
||||||
|
// TODO: Insecure by default. Might consider updating this for v1.0.0
|
||||||
flags.StringSliceVar(&cmd.cfg.AllowedOrigins, "allowed-origins", []string{"*"}, "Specifies a list of origins permitted to access this server. Defaults to '*'.")
|
flags.StringSliceVar(&cmd.cfg.AllowedOrigins, "allowed-origins", []string{"*"}, "Specifies a list of origins permitted to access this server. Defaults to '*'.")
|
||||||
|
flags.StringSliceVar(&cmd.cfg.AllowedHosts, "allowed-hosts", []string{"*"}, "Specifies a list of hosts permitted to access this server. Defaults to '*'.")
|
||||||
|
|
||||||
// wrap RunE command so that we have access to original Command object
|
// wrap RunE command so that we have access to original Command object
|
||||||
cmd.RunE = func(*cobra.Command, []string) error { return run(cmd) }
|
cmd.RunE = func(*cobra.Command, []string) error { return run(cmd) }
|
||||||
@@ -944,6 +951,7 @@ func run(cmd *Command) error {
|
|||||||
|
|
||||||
cmd.cfg.SourceConfigs = finalToolsFile.Sources
|
cmd.cfg.SourceConfigs = finalToolsFile.Sources
|
||||||
cmd.cfg.AuthServiceConfigs = finalToolsFile.AuthServices
|
cmd.cfg.AuthServiceConfigs = finalToolsFile.AuthServices
|
||||||
|
cmd.cfg.EmbeddingModelConfigs = finalToolsFile.EmbeddingModels
|
||||||
cmd.cfg.ToolConfigs = finalToolsFile.Tools
|
cmd.cfg.ToolConfigs = finalToolsFile.Tools
|
||||||
cmd.cfg.ToolsetConfigs = finalToolsFile.Toolsets
|
cmd.cfg.ToolsetConfigs = finalToolsFile.Toolsets
|
||||||
cmd.cfg.PromptConfigs = finalToolsFile.Prompts
|
cmd.cfg.PromptConfigs = finalToolsFile.Prompts
|
||||||
|
|||||||
@@ -67,6 +67,9 @@ func withDefaults(c server.ServerConfig) server.ServerConfig {
|
|||||||
if c.AllowedOrigins == nil {
|
if c.AllowedOrigins == nil {
|
||||||
c.AllowedOrigins = []string{"*"}
|
c.AllowedOrigins = []string{"*"}
|
||||||
}
|
}
|
||||||
|
if c.AllowedHosts == nil {
|
||||||
|
c.AllowedHosts = []string{"*"}
|
||||||
|
}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,6 +223,13 @@ func TestServerConfigFlags(t *testing.T) {
|
|||||||
AllowedOrigins: []string{"http://foo.com", "http://bar.com"},
|
AllowedOrigins: []string{"http://foo.com", "http://bar.com"},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "allowed hosts",
|
||||||
|
args: []string{"--allowed-hosts", "http://foo.com,http://bar.com"},
|
||||||
|
want: withDefaults(server.ServerConfig{
|
||||||
|
AllowedHosts: []string{"http://foo.com", "http://bar.com"},
|
||||||
|
}),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range tcs {
|
for _, tc := range tcs {
|
||||||
t.Run(tc.desc, func(t *testing.T) {
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
@@ -1352,6 +1362,7 @@ func TestPrebuiltTools(t *testing.T) {
|
|||||||
cloudsqlmssqlobsvconfig, _ := prebuiltconfigs.Get("cloud-sql-mssql-observability")
|
cloudsqlmssqlobsvconfig, _ := prebuiltconfigs.Get("cloud-sql-mssql-observability")
|
||||||
serverless_spark_config, _ := prebuiltconfigs.Get("serverless-spark")
|
serverless_spark_config, _ := prebuiltconfigs.Get("serverless-spark")
|
||||||
cloudhealthcare_config, _ := prebuiltconfigs.Get("cloud-healthcare")
|
cloudhealthcare_config, _ := prebuiltconfigs.Get("cloud-healthcare")
|
||||||
|
snowflake_config, _ := prebuiltconfigs.Get("snowflake")
|
||||||
|
|
||||||
// Set environment variables
|
// Set environment variables
|
||||||
t.Setenv("API_KEY", "your_api_key")
|
t.Setenv("API_KEY", "your_api_key")
|
||||||
@@ -1449,6 +1460,14 @@ func TestPrebuiltTools(t *testing.T) {
|
|||||||
t.Setenv("CLOUD_HEALTHCARE_REGION", "your_gcp_region")
|
t.Setenv("CLOUD_HEALTHCARE_REGION", "your_gcp_region")
|
||||||
t.Setenv("CLOUD_HEALTHCARE_DATASET", "your_healthcare_dataset")
|
t.Setenv("CLOUD_HEALTHCARE_DATASET", "your_healthcare_dataset")
|
||||||
|
|
||||||
|
t.Setenv("SNOWFLAKE_ACCOUNT", "your_account")
|
||||||
|
t.Setenv("SNOWFLAKE_USER", "your_username")
|
||||||
|
t.Setenv("SNOWFLAKE_PASSWORD", "your_pass")
|
||||||
|
t.Setenv("SNOWFLAKE_DATABASE", "your_db")
|
||||||
|
t.Setenv("SNOWFLAKE_SCHEMA", "your_schema")
|
||||||
|
t.Setenv("SNOWFLAKE_WAREHOUSE", "your_wh")
|
||||||
|
t.Setenv("SNOWFLAKE_ROLE", "your_role")
|
||||||
|
|
||||||
ctx, err := testutils.ContextWithNewLogger()
|
ctx, err := testutils.ContextWithNewLogger()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %s", err)
|
t.Fatalf("unexpected error: %s", err)
|
||||||
@@ -1746,6 +1765,16 @@ func TestPrebuiltTools(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Snowflake prebuilt tool",
|
||||||
|
in: snowflake_config,
|
||||||
|
wantToolset: server.ToolsetConfigs{
|
||||||
|
"snowflake_tools": tools.ToolsetConfig{
|
||||||
|
Name: "snowflake_tools",
|
||||||
|
ToolNames: []string{"execute_sql", "list_tables"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tcs {
|
for _, tc := range tcs {
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
0.24.0
|
0.25.0
|
||||||
|
|||||||
59
docs/SPANNERADMIN_README.md
Normal file
59
docs/SPANNERADMIN_README.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# Cloud Spanner Admin MCP Server
|
||||||
|
|
||||||
|
The Cloud Spanner Admin Model Context Protocol (MCP) Server gives AI-powered development tools the ability to manage your Google Cloud Spanner infrastructure. It supports creating instances.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
An editor configured to use the Cloud Spanner Admin MCP server can use its AI capabilities to help you:
|
||||||
|
|
||||||
|
- **Provision & Manage Infrastructure** - Create Cloud Spanner instances
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
* [Node.js](https://nodejs.org/) installed.
|
||||||
|
* A Google Cloud project with the **Cloud Spanner Admin API** enabled.
|
||||||
|
* Ensure [Application Default Credentials](https://cloud.google.com/docs/authentication/gcloud) are available in your environment.
|
||||||
|
* IAM Permissions:
|
||||||
|
* Cloud Spanner Admin (`roles/spanner.admin`)
|
||||||
|
|
||||||
|
## Install & Configuration
|
||||||
|
|
||||||
|
In the Antigravity MCP Store, click the "Install" button.
|
||||||
|
|
||||||
|
You'll now be able to see all enabled tools in the "Tools" tab.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> If you encounter issues with Windows Defender blocking the execution, you may need to configure an allowlist. See [Configure exclusions for Microsoft Defender Antivirus](https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/configure-exclusions-microsoft-defender-antivirus?view=o365-worldwide) for more details.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Once configured, the MCP server will automatically provide Cloud Spanner Admin capabilities to your AI assistant. You can:
|
||||||
|
|
||||||
|
* "Create a new Spanner instance named 'my-spanner-instance' in the 'my-gcp-project' project with config 'regional-us-central1', edition 'ENTERPRISE', and 1 node."
|
||||||
|
|
||||||
|
## Server Capabilities
|
||||||
|
|
||||||
|
The Cloud Spanner Admin MCP server provides the following tools:
|
||||||
|
|
||||||
|
| Tool Name | Description |
|
||||||
|
|:------------------|:---------------------------------|
|
||||||
|
| `create_instance` | Create a Cloud Spanner instance. |
|
||||||
|
|
||||||
|
## Custom MCP Server Configuration
|
||||||
|
|
||||||
|
Add the following configuration to your MCP client (e.g., `settings.json` for Gemini CLI, `mcp_config.json` for Antigravity):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"spanner-admin": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": ["-y", "@toolbox-sdk/server", "--prebuilt", "spanner-admin", "--stdio"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
For more information, visit the [Cloud Spanner Admin API documentation](https://cloud.google.com/spanner/docs/reference/rpc/google.spanner.admin.instance.v1).
|
||||||
18
docs/en/blogs/_index.md
Normal file
18
docs/en/blogs/_index.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
title: "Featured Articles"
|
||||||
|
weight: 3
|
||||||
|
description: Toolbox Medium Blogs
|
||||||
|
manualLink: "https://medium.com/@mcp_toolbox"
|
||||||
|
manualLinkTarget: _blank
|
||||||
|
---
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Redirecting to Featured Articles</title>
|
||||||
|
<link rel="canonical" href="https://medium.com/@mcp_toolbox"/>
|
||||||
|
<meta http-equiv="refresh" content="0;url=https://medium.com/@mcp_toolbox"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>If you are not automatically redirected, please <a href="https://medium.com/@mcp_toolbox">follow this link to our articles</a>.</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -234,7 +234,7 @@
|
|||||||
},
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"version = \"0.24.0\" # x-release-please-version\n",
|
"version = \"0.25.0\" # x-release-please-version\n",
|
||||||
"! curl -O https://storage.googleapis.com/genai-toolbox/v{version}/linux/amd64/toolbox\n",
|
"! curl -O https://storage.googleapis.com/genai-toolbox/v{version}/linux/amd64/toolbox\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# Make the binary executable\n",
|
"# Make the binary executable\n",
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ To install Toolbox as a binary on Linux (AMD64):
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
# see releases page for other versions
|
# see releases page for other versions
|
||||||
export VERSION=0.24.0
|
export VERSION=0.25.0
|
||||||
curl -L -o toolbox https://storage.googleapis.com/genai-toolbox/v$VERSION/linux/amd64/toolbox
|
curl -L -o toolbox https://storage.googleapis.com/genai-toolbox/v$VERSION/linux/amd64/toolbox
|
||||||
chmod +x toolbox
|
chmod +x toolbox
|
||||||
```
|
```
|
||||||
@@ -114,7 +114,7 @@ To install Toolbox as a binary on macOS (Apple Silicon):
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
# see releases page for other versions
|
# see releases page for other versions
|
||||||
export VERSION=0.24.0
|
export VERSION=0.25.0
|
||||||
curl -L -o toolbox https://storage.googleapis.com/genai-toolbox/v$VERSION/darwin/arm64/toolbox
|
curl -L -o toolbox https://storage.googleapis.com/genai-toolbox/v$VERSION/darwin/arm64/toolbox
|
||||||
chmod +x toolbox
|
chmod +x toolbox
|
||||||
```
|
```
|
||||||
@@ -125,7 +125,7 @@ To install Toolbox as a binary on macOS (Intel):
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
# see releases page for other versions
|
# see releases page for other versions
|
||||||
export VERSION=0.24.0
|
export VERSION=0.25.0
|
||||||
curl -L -o toolbox https://storage.googleapis.com/genai-toolbox/v$VERSION/darwin/amd64/toolbox
|
curl -L -o toolbox https://storage.googleapis.com/genai-toolbox/v$VERSION/darwin/amd64/toolbox
|
||||||
chmod +x toolbox
|
chmod +x toolbox
|
||||||
```
|
```
|
||||||
@@ -136,7 +136,7 @@ To install Toolbox as a binary on Windows (Command Prompt):
|
|||||||
|
|
||||||
```cmd
|
```cmd
|
||||||
:: see releases page for other versions
|
:: see releases page for other versions
|
||||||
set VERSION=0.24.0
|
set VERSION=0.25.0
|
||||||
curl -o toolbox.exe "https://storage.googleapis.com/genai-toolbox/v%VERSION%/windows/amd64/toolbox.exe"
|
curl -o toolbox.exe "https://storage.googleapis.com/genai-toolbox/v%VERSION%/windows/amd64/toolbox.exe"
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -146,7 +146,7 @@ To install Toolbox as a binary on Windows (PowerShell):
|
|||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# see releases page for other versions
|
# see releases page for other versions
|
||||||
$VERSION = "0.24.0"
|
$VERSION = "0.25.0"
|
||||||
curl.exe -o toolbox.exe "https://storage.googleapis.com/genai-toolbox/v$VERSION/windows/amd64/toolbox.exe"
|
curl.exe -o toolbox.exe "https://storage.googleapis.com/genai-toolbox/v$VERSION/windows/amd64/toolbox.exe"
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -158,7 +158,7 @@ You can also install Toolbox as a container:
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
# see releases page for other versions
|
# see releases page for other versions
|
||||||
export VERSION=0.24.0
|
export VERSION=0.25.0
|
||||||
docker pull us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:$VERSION
|
docker pull us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:$VERSION
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -177,7 +177,7 @@ To install from source, ensure you have the latest version of
|
|||||||
[Go installed](https://go.dev/doc/install), and then run the following command:
|
[Go installed](https://go.dev/doc/install), and then run the following command:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
go install github.com/googleapis/genai-toolbox@v0.24.0
|
go install github.com/googleapis/genai-toolbox@v0.25.0
|
||||||
```
|
```
|
||||||
|
|
||||||
{{% /tab %}}
|
{{% /tab %}}
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ In this section, we will download Toolbox, configure our tools in a
|
|||||||
<!-- {x-release-please-start-version} -->
|
<!-- {x-release-please-start-version} -->
|
||||||
```bash
|
```bash
|
||||||
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
|
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
|
||||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/$OS/toolbox
|
curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/$OS/toolbox
|
||||||
```
|
```
|
||||||
<!-- {x-release-please-end} -->
|
<!-- {x-release-please-end} -->
|
||||||
|
|
||||||
|
|||||||
@@ -569,11 +569,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jws": {
|
"node_modules/jws": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
|
||||||
"integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
|
"integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jwa": "^2.0.0",
|
"jwa": "^2.0.1",
|
||||||
"safe-buffer": "^5.0.1"
|
"safe-buffer": "^5.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -882,11 +882,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jws": {
|
"node_modules/jws": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
|
||||||
"integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
|
"integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jwa": "^2.0.0",
|
"jwa": "^2.0.1",
|
||||||
"safe-buffer": "^5.0.1"
|
"safe-buffer": "^5.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
google-genai==1.56.0
|
google-genai==1.57.0
|
||||||
toolbox-core==0.5.4
|
toolbox-core==0.5.4
|
||||||
pytest==9.0.2
|
pytest==9.0.2
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
langchain==1.2.0
|
langchain==1.2.2
|
||||||
langchain-google-vertexai==3.2.0
|
langchain-google-vertexai==3.2.1
|
||||||
langgraph==1.0.5
|
langgraph==1.0.5
|
||||||
toolbox-langchain==0.5.4
|
toolbox-langchain==0.5.4
|
||||||
pytest==9.0.2
|
pytest==9.0.2
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ In this section, we will download Toolbox, configure our tools in a
|
|||||||
<!-- {x-release-please-start-version} -->
|
<!-- {x-release-please-start-version} -->
|
||||||
```bash
|
```bash
|
||||||
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
|
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
|
||||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/$OS/toolbox
|
curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/$OS/toolbox
|
||||||
```
|
```
|
||||||
<!-- {x-release-please-end} -->
|
<!-- {x-release-please-end} -->
|
||||||
|
|
||||||
|
|||||||
@@ -100,19 +100,19 @@ After you install Looker in the MCP Store, resources and tools from the server a
|
|||||||
|
|
||||||
{{< tabpane persist=header >}}
|
{{< tabpane persist=header >}}
|
||||||
{{< tab header="linux/amd64" lang="bash" >}}
|
{{< tab header="linux/amd64" lang="bash" >}}
|
||||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/linux/amd64/toolbox
|
curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/linux/amd64/toolbox
|
||||||
{{< /tab >}}
|
{{< /tab >}}
|
||||||
|
|
||||||
{{< tab header="darwin/arm64" lang="bash" >}}
|
{{< tab header="darwin/arm64" lang="bash" >}}
|
||||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/darwin/arm64/toolbox
|
curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/darwin/arm64/toolbox
|
||||||
{{< /tab >}}
|
{{< /tab >}}
|
||||||
|
|
||||||
{{< tab header="darwin/amd64" lang="bash" >}}
|
{{< tab header="darwin/amd64" lang="bash" >}}
|
||||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/darwin/amd64/toolbox
|
curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/darwin/amd64/toolbox
|
||||||
{{< /tab >}}
|
{{< /tab >}}
|
||||||
|
|
||||||
{{< tab header="windows/amd64" lang="bash" >}}
|
{{< tab header="windows/amd64" lang="bash" >}}
|
||||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/windows/amd64/toolbox.exe
|
curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/windows/amd64/toolbox.exe
|
||||||
{{< /tab >}}
|
{{< /tab >}}
|
||||||
{{< /tabpane >}}
|
{{< /tabpane >}}
|
||||||
<!-- {x-release-please-end} -->
|
<!-- {x-release-please-end} -->
|
||||||
|
|||||||
@@ -45,19 +45,19 @@ instance:
|
|||||||
<!-- {x-release-please-start-version} -->
|
<!-- {x-release-please-start-version} -->
|
||||||
{{< tabpane persist=header >}}
|
{{< tabpane persist=header >}}
|
||||||
{{< tab header="linux/amd64" lang="bash" >}}
|
{{< tab header="linux/amd64" lang="bash" >}}
|
||||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/linux/amd64/toolbox
|
curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/linux/amd64/toolbox
|
||||||
{{< /tab >}}
|
{{< /tab >}}
|
||||||
|
|
||||||
{{< tab header="darwin/arm64" lang="bash" >}}
|
{{< tab header="darwin/arm64" lang="bash" >}}
|
||||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/darwin/arm64/toolbox
|
curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/darwin/arm64/toolbox
|
||||||
{{< /tab >}}
|
{{< /tab >}}
|
||||||
|
|
||||||
{{< tab header="darwin/amd64" lang="bash" >}}
|
{{< tab header="darwin/amd64" lang="bash" >}}
|
||||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/darwin/amd64/toolbox
|
curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/darwin/amd64/toolbox
|
||||||
{{< /tab >}}
|
{{< /tab >}}
|
||||||
|
|
||||||
{{< tab header="windows/amd64" lang="bash" >}}
|
{{< tab header="windows/amd64" lang="bash" >}}
|
||||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/windows/amd64/toolbox.exe
|
curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/windows/amd64/toolbox.exe
|
||||||
{{< /tab >}}
|
{{< /tab >}}
|
||||||
{{< /tabpane >}}
|
{{< /tabpane >}}
|
||||||
<!-- {x-release-please-end} -->
|
<!-- {x-release-please-end} -->
|
||||||
|
|||||||
@@ -43,19 +43,19 @@ expose your developer assistant tools to a MySQL instance:
|
|||||||
<!-- {x-release-please-start-version} -->
|
<!-- {x-release-please-start-version} -->
|
||||||
{{< tabpane persist=header >}}
|
{{< tabpane persist=header >}}
|
||||||
{{< tab header="linux/amd64" lang="bash" >}}
|
{{< tab header="linux/amd64" lang="bash" >}}
|
||||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/linux/amd64/toolbox
|
curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/linux/amd64/toolbox
|
||||||
{{< /tab >}}
|
{{< /tab >}}
|
||||||
|
|
||||||
{{< tab header="darwin/arm64" lang="bash" >}}
|
{{< tab header="darwin/arm64" lang="bash" >}}
|
||||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/darwin/arm64/toolbox
|
curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/darwin/arm64/toolbox
|
||||||
{{< /tab >}}
|
{{< /tab >}}
|
||||||
|
|
||||||
{{< tab header="darwin/amd64" lang="bash" >}}
|
{{< tab header="darwin/amd64" lang="bash" >}}
|
||||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/darwin/amd64/toolbox
|
curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/darwin/amd64/toolbox
|
||||||
{{< /tab >}}
|
{{< /tab >}}
|
||||||
|
|
||||||
{{< tab header="windows/amd64" lang="bash" >}}
|
{{< tab header="windows/amd64" lang="bash" >}}
|
||||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/windows/amd64/toolbox.exe
|
curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/windows/amd64/toolbox.exe
|
||||||
{{< /tab >}}
|
{{< /tab >}}
|
||||||
{{< /tabpane >}}
|
{{< /tabpane >}}
|
||||||
<!-- {x-release-please-end} -->
|
<!-- {x-release-please-end} -->
|
||||||
|
|||||||
@@ -44,19 +44,19 @@ expose your developer assistant tools to a Neo4j instance:
|
|||||||
<!-- {x-release-please-start-version} -->
|
<!-- {x-release-please-start-version} -->
|
||||||
{{< tabpane persist=header >}}
|
{{< tabpane persist=header >}}
|
||||||
{{< tab header="linux/amd64" lang="bash" >}}
|
{{< tab header="linux/amd64" lang="bash" >}}
|
||||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/linux/amd64/toolbox
|
curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/linux/amd64/toolbox
|
||||||
{{< /tab >}}
|
{{< /tab >}}
|
||||||
|
|
||||||
{{< tab header="darwin/arm64" lang="bash" >}}
|
{{< tab header="darwin/arm64" lang="bash" >}}
|
||||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/darwin/arm64/toolbox
|
curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/darwin/arm64/toolbox
|
||||||
{{< /tab >}}
|
{{< /tab >}}
|
||||||
|
|
||||||
{{< tab header="darwin/amd64" lang="bash" >}}
|
{{< tab header="darwin/amd64" lang="bash" >}}
|
||||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/darwin/amd64/toolbox
|
curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/darwin/amd64/toolbox
|
||||||
{{< /tab >}}
|
{{< /tab >}}
|
||||||
|
|
||||||
{{< tab header="windows/amd64" lang="bash" >}}
|
{{< tab header="windows/amd64" lang="bash" >}}
|
||||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/windows/amd64/toolbox.exe
|
curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/windows/amd64/toolbox.exe
|
||||||
{{< /tab >}}
|
{{< /tab >}}
|
||||||
{{< /tabpane >}}
|
{{< /tabpane >}}
|
||||||
<!-- {x-release-please-end} -->
|
<!-- {x-release-please-end} -->
|
||||||
|
|||||||
@@ -56,19 +56,19 @@ Omni](https://cloud.google.com/alloydb/omni/current/docs/overview).
|
|||||||
<!-- {x-release-please-start-version} -->
|
<!-- {x-release-please-start-version} -->
|
||||||
{{< tabpane persist=header >}}
|
{{< tabpane persist=header >}}
|
||||||
{{< tab header="linux/amd64" lang="bash" >}}
|
{{< tab header="linux/amd64" lang="bash" >}}
|
||||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/linux/amd64/toolbox
|
curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/linux/amd64/toolbox
|
||||||
{{< /tab >}}
|
{{< /tab >}}
|
||||||
|
|
||||||
{{< tab header="darwin/arm64" lang="bash" >}}
|
{{< tab header="darwin/arm64" lang="bash" >}}
|
||||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/darwin/arm64/toolbox
|
curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/darwin/arm64/toolbox
|
||||||
{{< /tab >}}
|
{{< /tab >}}
|
||||||
|
|
||||||
{{< tab header="darwin/amd64" lang="bash" >}}
|
{{< tab header="darwin/amd64" lang="bash" >}}
|
||||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/darwin/amd64/toolbox
|
curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/darwin/amd64/toolbox
|
||||||
{{< /tab >}}
|
{{< /tab >}}
|
||||||
|
|
||||||
{{< tab header="windows/amd64" lang="bash" >}}
|
{{< tab header="windows/amd64" lang="bash" >}}
|
||||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/windows/amd64/toolbox.exe
|
curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/windows/amd64/toolbox.exe
|
||||||
{{< /tab >}}
|
{{< /tab >}}
|
||||||
{{< /tabpane >}}
|
{{< /tabpane >}}
|
||||||
<!-- {x-release-please-end} -->
|
<!-- {x-release-please-end} -->
|
||||||
|
|||||||
@@ -43,19 +43,19 @@ to expose your developer assistant tools to a SQLite instance:
|
|||||||
<!-- {x-release-please-start-version} -->
|
<!-- {x-release-please-start-version} -->
|
||||||
{{< tabpane persist=header >}}
|
{{< tabpane persist=header >}}
|
||||||
{{< tab header="linux/amd64" lang="bash" >}}
|
{{< tab header="linux/amd64" lang="bash" >}}
|
||||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/linux/amd64/toolbox
|
curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/linux/amd64/toolbox
|
||||||
{{< /tab >}}
|
{{< /tab >}}
|
||||||
|
|
||||||
{{< tab header="darwin/arm64" lang="bash" >}}
|
{{< tab header="darwin/arm64" lang="bash" >}}
|
||||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/darwin/arm64/toolbox
|
curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/darwin/arm64/toolbox
|
||||||
{{< /tab >}}
|
{{< /tab >}}
|
||||||
|
|
||||||
{{< tab header="darwin/amd64" lang="bash" >}}
|
{{< tab header="darwin/amd64" lang="bash" >}}
|
||||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/darwin/amd64/toolbox
|
curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/darwin/amd64/toolbox
|
||||||
{{< /tab >}}
|
{{< /tab >}}
|
||||||
|
|
||||||
{{< tab header="windows/amd64" lang="bash" >}}
|
{{< tab header="windows/amd64" lang="bash" >}}
|
||||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/windows/amd64/toolbox.exe
|
curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/windows/amd64/toolbox.exe
|
||||||
{{< /tab >}}
|
{{< /tab >}}
|
||||||
{{< /tabpane >}}
|
{{< /tabpane >}}
|
||||||
<!-- {x-release-please-end} -->
|
<!-- {x-release-please-end} -->
|
||||||
|
|||||||
@@ -68,7 +68,12 @@ networks:
|
|||||||
```
|
```
|
||||||
|
|
||||||
{{< notice tip >}}
|
{{< notice tip >}}
|
||||||
To prevent DNS rebinding attack, use the `--allowed-origins` flag to specify a
|
To prevent DNS rebinding attack, use the `--allowed-hosts` flag to specify a
|
||||||
|
list of hosts for validation. E.g. `command: [ "toolbox",
|
||||||
|
"--tools-file", "/config/tools.yaml", "--address", "0.0.0.0",
|
||||||
|
"--allowed-hosts", "localhost:5000"]`
|
||||||
|
|
||||||
|
To implement CORs, use the `--allowed-origins` flag to specify a
|
||||||
list of origins permitted to access the server. E.g. `command: [ "toolbox",
|
list of origins permitted to access the server. E.g. `command: [ "toolbox",
|
||||||
"--tools-file", "/config/tools.yaml", "--address", "0.0.0.0",
|
"--tools-file", "/config/tools.yaml", "--address", "0.0.0.0",
|
||||||
"--allowed-origins", "https://foo.bar"]`
|
"--allowed-origins", "https://foo.bar"]`
|
||||||
|
|||||||
@@ -188,9 +188,13 @@ description: >
|
|||||||
path: tools.yaml
|
path: tools.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
{{< notice tip >}}
|
{{< notice tip >}}
|
||||||
To prevent DNS rebinding attack, use the `--allowed-origins` flag to specify a
|
To prevent DNS rebinding attack, use the `--allowed-origins` flag to specify a
|
||||||
list of origins permitted to access the server. E.g. `args: ["--address",
|
list of origins permitted to access the server. E.g. `args: ["--address",
|
||||||
|
"0.0.0.0", "--allowed-hosts", "foo.bar:5000"]`
|
||||||
|
|
||||||
|
To implement CORs, use the `--allowed-origins` flag to specify a
|
||||||
|
list of origins permitted to access the server. E.g. `args: ["--address",
|
||||||
"0.0.0.0", "--allowed-origins", "https://foo.bar"]`
|
"0.0.0.0", "--allowed-origins", "https://foo.bar"]`
|
||||||
{{< /notice >}}
|
{{< /notice >}}
|
||||||
|
|
||||||
|
|||||||
@@ -142,14 +142,18 @@ deployment will time out.
|
|||||||
|
|
||||||
### Update deployed server to be secure
|
### Update deployed server to be secure
|
||||||
|
|
||||||
To prevent DNS rebinding attack, use the `--allowed-origins` flag to specify a
|
To prevent DNS rebinding attack, use the `--allowed-hosts` flag to specify a
|
||||||
list of origins permitted to access the server. In order to do that, you will
|
list of hosts. In order to do that, you will
|
||||||
have to re-deploy the cloud run service with the new flag.
|
have to re-deploy the cloud run service with the new flag.
|
||||||
|
|
||||||
|
To implement CORs checks, use the `--allowed-origins` flag to specify a list of
|
||||||
|
origins permitted to access the server.
|
||||||
|
|
||||||
1. Set an environment variable to the cloud run url:
|
1. Set an environment variable to the cloud run url:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export URL=<cloud run url>
|
export URL=<cloud run url>
|
||||||
|
export HOST=<cloud run host>
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Redeploy Toolbox:
|
2. Redeploy Toolbox:
|
||||||
@@ -160,7 +164,7 @@ have to re-deploy the cloud run service with the new flag.
|
|||||||
--service-account toolbox-identity \
|
--service-account toolbox-identity \
|
||||||
--region us-central1 \
|
--region us-central1 \
|
||||||
--set-secrets "/app/tools.yaml=tools:latest" \
|
--set-secrets "/app/tools.yaml=tools:latest" \
|
||||||
--args="--tools-file=/app/tools.yaml","--address=0.0.0.0","--port=8080","--allowed-origins=$URL"
|
--args="--tools-file=/app/tools.yaml","--address=0.0.0.0","--port=8080","--allowed-origins=$URL","--allowed-hosts=$HOST"
|
||||||
# --allow-unauthenticated # https://cloud.google.com/run/docs/authenticating/public#gcloud
|
# --allow-unauthenticated # https://cloud.google.com/run/docs/authenticating/public#gcloud
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -172,7 +176,7 @@ have to re-deploy the cloud run service with the new flag.
|
|||||||
--service-account toolbox-identity \
|
--service-account toolbox-identity \
|
||||||
--region us-central1 \
|
--region us-central1 \
|
||||||
--set-secrets "/app/tools.yaml=tools:latest" \
|
--set-secrets "/app/tools.yaml=tools:latest" \
|
||||||
--args="--tools-file=/app/tools.yaml","--address=0.0.0.0","--port=8080","--allowed-origins=$URL" \
|
--args="--tools-file=/app/tools.yaml","--address=0.0.0.0","--port=8080","--allowed-origins=$URL","--allowed-hosts=$HOST" \
|
||||||
# TODO(dev): update the following to match your VPC if necessary
|
# TODO(dev): update the following to match your VPC if necessary
|
||||||
--network default \
|
--network default \
|
||||||
--subnet default
|
--subnet default
|
||||||
|
|||||||
@@ -8,25 +8,26 @@ description: >
|
|||||||
|
|
||||||
## Reference
|
## Reference
|
||||||
|
|
||||||
| Flag (Short) | Flag (Long) | Description | Default |
|
| Flag (Short) | Flag (Long) | Description | Default |
|
||||||
|--------------|----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------|
|
|--------------|----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------|
|
||||||
| `-a` | `--address` | Address of the interface the server will listen on. | `127.0.0.1` |
|
| `-a` | `--address` | Address of the interface the server will listen on. | `127.0.0.1` |
|
||||||
| | `--disable-reload` | Disables dynamic reloading of tools file. | |
|
| | `--disable-reload` | Disables dynamic reloading of tools file. | |
|
||||||
| `-h` | `--help` | help for toolbox | |
|
| `-h` | `--help` | help for toolbox | |
|
||||||
| | `--log-level` | Specify the minimum level logged. Allowed: 'DEBUG', 'INFO', 'WARN', 'ERROR'. | `info` |
|
| | `--log-level` | Specify the minimum level logged. Allowed: 'DEBUG', 'INFO', 'WARN', 'ERROR'. | `info` |
|
||||||
| | `--logging-format` | Specify logging format to use. Allowed: 'standard' or 'JSON'. | `standard` |
|
| | `--logging-format` | Specify logging format to use. Allowed: 'standard' or 'JSON'. | `standard` |
|
||||||
| `-p` | `--port` | Port the server will listen on. | `5000` |
|
| `-p` | `--port` | Port the server will listen on. | `5000` |
|
||||||
| | `--prebuilt` | Use a prebuilt tool configuration by source type. See [Prebuilt Tools Reference](prebuilt-tools.md) for allowed values. | |
|
| | `--prebuilt` | Use a prebuilt tool configuration by source type. See [Prebuilt Tools Reference](prebuilt-tools.md) for allowed values. | |
|
||||||
| | `--stdio` | Listens via MCP STDIO instead of acting as a remote HTTP server. | |
|
| | `--stdio` | Listens via MCP STDIO instead of acting as a remote HTTP server. | |
|
||||||
| | `--telemetry-gcp` | Enable exporting directly to Google Cloud Monitoring. | |
|
| | `--telemetry-gcp` | Enable exporting directly to Google Cloud Monitoring. | |
|
||||||
| | `--telemetry-otlp` | Enable exporting using OpenTelemetry Protocol (OTLP) to the specified endpoint (e.g. 'http://127.0.0.1:4318') | |
|
| | `--telemetry-otlp` | Enable exporting using OpenTelemetry Protocol (OTLP) to the specified endpoint (e.g. 'http://127.0.0.1:4318') | |
|
||||||
| | `--telemetry-service-name` | Sets the value of the service.name resource attribute for telemetry data. | `toolbox` |
|
| | `--telemetry-service-name` | Sets the value of the service.name resource attribute for telemetry data. | `toolbox` |
|
||||||
| | `--tools-file` | File path specifying the tool configuration. Cannot be used with --tools-files or --tools-folder. | |
|
| | `--tools-file` | File path specifying the tool configuration. Cannot be used with --tools-files or --tools-folder. | |
|
||||||
| | `--tools-files` | Multiple file paths specifying tool configurations. Files will be merged. Cannot be used with --tools-file or --tools-folder. | |
|
| | `--tools-files` | Multiple file paths specifying tool configurations. Files will be merged. Cannot be used with --tools-file or --tools-folder. | |
|
||||||
| | `--tools-folder` | Directory path containing YAML tool configuration files. All .yaml and .yml files in the directory will be loaded and merged. Cannot be used with --tools-file or --tools-files. | |
|
| | `--tools-folder` | Directory path containing YAML tool configuration files. All .yaml and .yml files in the directory will be loaded and merged. Cannot be used with --tools-file or --tools-files. | |
|
||||||
| | `--ui` | Launches the Toolbox UI web server. | |
|
| | `--ui` | Launches the Toolbox UI web server. | |
|
||||||
| | `--allowed-origins` | Specifies a list of origins permitted to access this server. | `*` |
|
| | `--allowed-origins` | Specifies a list of origins permitted to access this server for CORs access. | `*` |
|
||||||
| `-v` | `--version` | version for toolbox | |
|
| | `--allowed-hosts` | Specifies a list of hosts permitted to access this server to prevent DNS rebinding attacks. | `*` |
|
||||||
|
| `-v` | `--version` | version for toolbox | |
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
|||||||
63
docs/en/resources/sources/snowflake.md
Normal file
63
docs/en/resources/sources/snowflake.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
---
|
||||||
|
title: "Snowflake"
|
||||||
|
type: docs
|
||||||
|
weight: 1
|
||||||
|
description: >
|
||||||
|
Snowflake is a cloud-based data platform.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
[Snowflake][sf-docs] is a cloud data platform that provides a data warehouse-as-a-service designed for the cloud.
|
||||||
|
|
||||||
|
[sf-docs]: https://docs.snowflake.com/
|
||||||
|
|
||||||
|
## Available Tools
|
||||||
|
|
||||||
|
- [`snowflake-sql`](../tools/snowflake/snowflake-sql.md)
|
||||||
|
Execute SQL queries as prepared statements in Snowflake.
|
||||||
|
|
||||||
|
- [`snowflake-execute-sql`](../tools/snowflake/snowflake-execute-sql.md)
|
||||||
|
Run parameterized SQL statements in Snowflake.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
### Database User
|
||||||
|
|
||||||
|
This source only uses standard authentication. You will need to create a
|
||||||
|
Snowflake user to login to the database with.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
sources:
|
||||||
|
my-sf-source:
|
||||||
|
kind: snowflake
|
||||||
|
account: ${SNOWFLAKE_ACCOUNT}
|
||||||
|
user: ${SNOWFLAKE_USER}
|
||||||
|
password: ${SNOWFLAKE_PASSWORD}
|
||||||
|
database: ${SNOWFLAKE_DATABASE}
|
||||||
|
schema: ${SNOWFLAKE_SCHEMA}
|
||||||
|
warehouse: ${SNOWFLAKE_WAREHOUSE}
|
||||||
|
role: ${SNOWFLAKE_ROLE}
|
||||||
|
```
|
||||||
|
|
||||||
|
{{< notice tip >}}
|
||||||
|
Use environment variable replacement with the format ${ENV_NAME}
|
||||||
|
instead of hardcoding your secrets into the configuration file.
|
||||||
|
{{< /notice >}}
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
| **field** | **type** | **required** | **description** |
|
||||||
|
|-----------|:--------:|:------------:|------------------------------------------------------------------------|
|
||||||
|
| kind | string | true | Must be "snowflake". |
|
||||||
|
| account | string | true | Your Snowflake account identifier. |
|
||||||
|
| user | string | true | Name of the Snowflake user to connect as (e.g. "my-sf-user"). |
|
||||||
|
| password | string | true | Password of the Snowflake user (e.g. "my-password"). |
|
||||||
|
| database | string | true | Name of the Snowflake database to connect to (e.g. "my_db"). |
|
||||||
|
| schema | string | true | Name of the schema to use (e.g. "my_schema"). |
|
||||||
|
| warehouse | string | false | The virtual warehouse to use. Defaults to "COMPUTE_WH". |
|
||||||
|
| role | string | false | The security role to use. Defaults to "ACCOUNTADMIN". |
|
||||||
|
| timeout | integer | false | The connection timeout in seconds. Defaults to 60. |
|
||||||
42
docs/en/resources/sources/spanner-admin.md
Normal file
42
docs/en/resources/sources/spanner-admin.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
title: Spanner Admin
|
||||||
|
type: docs
|
||||||
|
weight: 1
|
||||||
|
description: "A \"spanner-admin\" source provides a client for the Cloud Spanner Admin API.\n"
|
||||||
|
alias: [/resources/sources/spanner-admin]
|
||||||
|
---
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
The `spanner-admin` source provides a client to interact with the [Google
|
||||||
|
Cloud Spanner Admin API](https://cloud.google.com/spanner/docs/reference/rpc/google.spanner.admin.instance.v1). This
|
||||||
|
allows tools to perform administrative tasks on Spanner instances, such as
|
||||||
|
creating instances.
|
||||||
|
|
||||||
|
Authentication can be handled in two ways:
|
||||||
|
|
||||||
|
1. **Application Default Credentials (ADC):** By default, the source uses ADC
|
||||||
|
to authenticate with the API.
|
||||||
|
2. **Client-side OAuth:** If `useClientOAuth` is set to `true`, the source will
|
||||||
|
expect an OAuth 2.0 access token to be provided by the client (e.g., a web
|
||||||
|
browser) for each request.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
sources:
|
||||||
|
my-spanner-admin:
|
||||||
|
kind: spanner-admin
|
||||||
|
|
||||||
|
my-oauth-spanner-admin:
|
||||||
|
kind: spanner-admin
|
||||||
|
useClientOAuth: true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
| **field** | **type** | **required** | **description** |
|
||||||
|
| -------------- | :------: | :----------: | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| kind | string | true | Must be "spanner-admin". |
|
||||||
|
| defaultProject | string | false | The Google Cloud project ID to use for Spanner infrastructure tools. |
|
||||||
|
| useClientOAuth | boolean | false | If true, the source will use client-side OAuth for authorization. Otherwise, it will use Application Default Credentials. Defaults to `false`. |
|
||||||
@@ -50,16 +50,19 @@ instead of hardcoding your secrets into the configuration file.
|
|||||||
|
|
||||||
## Reference
|
## Reference
|
||||||
|
|
||||||
| **field** | **type** | **required** | **description** |
|
| **field** | **type** | **required** | **description** |
|
||||||
|-----------------|:--------:|:------------:|------------------------------------------------------------------------------|
|
| ---------------------- | :------: | :----------: | ---------------------------------------------------------------------------- |
|
||||||
| kind | string | true | Must be "trino". |
|
| kind | string | true | Must be "trino". |
|
||||||
| host | string | true | Trino coordinator hostname (e.g. "trino.example.com") |
|
| host | string | true | Trino coordinator hostname (e.g. "trino.example.com") |
|
||||||
| port | string | true | Trino coordinator port (e.g. "8080", "8443") |
|
| port | string | true | Trino coordinator port (e.g. "8080", "8443") |
|
||||||
| user | string | false | Username for authentication (e.g. "analyst"). Optional for anonymous access. |
|
| user | string | false | Username for authentication (e.g. "analyst"). Optional for anonymous access. |
|
||||||
| password | string | false | Password for basic authentication |
|
| password | string | false | Password for basic authentication |
|
||||||
| catalog | string | true | Default catalog to use for queries (e.g. "hive") |
|
| catalog | string | true | Default catalog to use for queries (e.g. "hive") |
|
||||||
| schema | string | true | Default schema to use for queries (e.g. "default") |
|
| schema | string | true | Default schema to use for queries (e.g. "default") |
|
||||||
| queryTimeout | string | false | Query timeout duration (e.g. "30m", "1h") |
|
| queryTimeout | string | false | Query timeout duration (e.g. "30m", "1h") |
|
||||||
| accessToken | string | false | JWT access token for authentication |
|
| accessToken | string | false | JWT access token for authentication |
|
||||||
| kerberosEnabled | boolean | false | Enable Kerberos authentication (default: false) |
|
| kerberosEnabled | boolean | false | Enable Kerberos authentication (default: false) |
|
||||||
| sslEnabled | boolean | false | Enable SSL/TLS (default: false) |
|
| sslEnabled | boolean | false | Enable SSL/TLS (default: false) |
|
||||||
|
| disableSslVerification | boolean | false | Skip SSL/TLS certificate verification (default: false) |
|
||||||
|
| sslCertPath | string | false | Path to a custom SSL/TLS certificate file |
|
||||||
|
| sslCert | string | false | Custom SSL/TLS certificate content |
|
||||||
|
|||||||
7
docs/en/resources/tools/snowflake/_index.md
Normal file
7
docs/en/resources/tools/snowflake/_index.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
title: "Snowflake"
|
||||||
|
type: docs
|
||||||
|
weight: 1
|
||||||
|
description: >
|
||||||
|
Tools that work with Snowflake Sources.
|
||||||
|
---
|
||||||
40
docs/en/resources/tools/snowflake/snowflake-execute-sql.md
Normal file
40
docs/en/resources/tools/snowflake/snowflake-execute-sql.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
title: "snowflake-execute-sql"
|
||||||
|
type: docs
|
||||||
|
weight: 1
|
||||||
|
description: >
|
||||||
|
A "snowflake-execute-sql" tool executes a SQL statement against a Snowflake
|
||||||
|
database.
|
||||||
|
---
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
A `snowflake-execute-sql` tool executes a SQL statement against a Snowflake
|
||||||
|
database. It's compatible with any of the following sources:
|
||||||
|
|
||||||
|
- [snowflake](../../sources/snowflake.md)
|
||||||
|
|
||||||
|
`snowflake-execute-sql` takes one input parameter `sql` and run the sql
|
||||||
|
statement against the `source`.
|
||||||
|
|
||||||
|
> **Note:** This tool is intended for developer assistant workflows with
|
||||||
|
> human-in-the-loop and shouldn't be used for production agents.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
tools:
|
||||||
|
execute_sql_tool:
|
||||||
|
kind: snowflake-execute-sql
|
||||||
|
source: my-snowflake-instance
|
||||||
|
description: Use this tool to execute sql statement.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
| **field** | **type** | **required** | **description** |
|
||||||
|
|--------------|:-------------:|:------------:|-----------------------------------------------------------|
|
||||||
|
| kind | string | true | Must be "snowflake-execute-sql". |
|
||||||
|
| source | string | true | Name of the source the SQL should execute on. |
|
||||||
|
| description | string | true | Description of the tool that is passed to the LLM. |
|
||||||
|
| authRequired | array[string] | false | List of auth services that are required to use this tool. |
|
||||||
103
docs/en/resources/tools/snowflake/snowflake-sql.md
Normal file
103
docs/en/resources/tools/snowflake/snowflake-sql.md
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
---
|
||||||
|
title: "snowflake-sql"
|
||||||
|
type: docs
|
||||||
|
weight: 1
|
||||||
|
description: >
|
||||||
|
A "snowflake-sql" tool executes a pre-defined SQL statement against a
|
||||||
|
Snowflake database.
|
||||||
|
---
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
A `snowflake-sql` tool executes a pre-defined SQL statement against a Snowflake
|
||||||
|
database. It's compatible with any of the following sources:
|
||||||
|
|
||||||
|
- [snowflake](../../sources/snowflake.md)
|
||||||
|
|
||||||
|
The specified SQL statement is executed as a prepared statement, and specified
|
||||||
|
parameters will be inserted according to their position: e.g. `:1` will be the
|
||||||
|
first parameter specified, `:2` will be the second parameter, and so on.
|
||||||
|
|
||||||
|
> **Note:** This tool uses parameterized queries to prevent SQL injections.
|
||||||
|
> Query parameters can be used as substitutes for arbitrary expressions.
|
||||||
|
> Parameters cannot be used as substitutes for identifiers, column names, table
|
||||||
|
> names, or other parts of the query.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
tools:
|
||||||
|
search_flights_by_number:
|
||||||
|
kind: snowflake-sql
|
||||||
|
source: my-snowflake-instance
|
||||||
|
statement: |
|
||||||
|
SELECT * FROM flights
|
||||||
|
WHERE airline = :1
|
||||||
|
AND flight_number = :2
|
||||||
|
LIMIT 10
|
||||||
|
description: |
|
||||||
|
Use this tool to get information for a specific flight.
|
||||||
|
Takes an airline code and flight number and returns info on the flight.
|
||||||
|
Do NOT use this tool with a flight id. Do NOT guess an airline code or flight number.
|
||||||
|
A airline code is a code for an airline service consisting of two-character
|
||||||
|
airline designator and followed by flight number, which is 1 to 4 digit number.
|
||||||
|
For example, if given CY 0123, the airline is "CY", and flight_number is "123".
|
||||||
|
Another example for this is DL 1234, the airline is "DL", and flight_number is "1234".
|
||||||
|
If the tool returns more than one option choose the date closes to today.
|
||||||
|
Example:
|
||||||
|
{{
|
||||||
|
"airline": "CY",
|
||||||
|
"flight_number": "888",
|
||||||
|
}}
|
||||||
|
Example:
|
||||||
|
{{
|
||||||
|
"airline": "DL",
|
||||||
|
"flight_number": "1234",
|
||||||
|
}}
|
||||||
|
parameters:
|
||||||
|
- name: airline
|
||||||
|
type: string
|
||||||
|
description: Airline unique 2 letter identifier
|
||||||
|
- name: flight_number
|
||||||
|
type: string
|
||||||
|
description: 1 to 4 digit number
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example with Template Parameters
|
||||||
|
|
||||||
|
> **Note:** This tool allows direct modifications to the SQL statement,
|
||||||
|
> including identifiers, column names, and table names. **This makes it more
|
||||||
|
> vulnerable to SQL injections**. Using basic parameters only (see above) is
|
||||||
|
> recommended for performance and safety reasons. For more details, please check
|
||||||
|
> [templateParameters](..#template-parameters).
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
tools:
|
||||||
|
list_table:
|
||||||
|
kind: snowflake
|
||||||
|
source: my-snowflake-instance
|
||||||
|
statement: |
|
||||||
|
SELECT * FROM {{.tableName}};
|
||||||
|
description: |
|
||||||
|
Use this tool to list all information from a specific table.
|
||||||
|
Example:
|
||||||
|
{{
|
||||||
|
"tableName": "flights",
|
||||||
|
}}
|
||||||
|
templateParameters:
|
||||||
|
- name: tableName
|
||||||
|
type: string
|
||||||
|
description: Table to select from
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
| **field** | **type** | **required** | **description** |
|
||||||
|
|--------------------|:--------------------------------------------:|:------------:|----------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| kind | string | true | Must be "snowflake-sql". |
|
||||||
|
| source | string | true | Name of the source the SQL should execute on. |
|
||||||
|
| description | string | true | Description of the tool that is passed to the LLM. |
|
||||||
|
| statement | string | true | SQL statement to execute on. |
|
||||||
|
| parameters | [parameters](../#specifying-parameters) | false | List of [parameters](../#specifying-parameters) that will be inserted into the SQL statement. |
|
||||||
|
| templateParameters | [templateParameters](..#template-parameters) | false | List of [templateParameters](..#template-parameters) that will be inserted into the SQL statement before executing prepared statement. |
|
||||||
|
| authRequired | array[string] | false | List of auth services that are required to use this tool. |
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
title: spanner-create-instance
|
||||||
|
type: docs
|
||||||
|
weight: 2
|
||||||
|
description: "Create a Cloud Spanner instance."
|
||||||
|
---
|
||||||
|
|
||||||
|
The `spanner-create-instance` tool creates a new Cloud Spanner instance in a
|
||||||
|
specified Google Cloud project.
|
||||||
|
|
||||||
|
{{< notice info >}}
|
||||||
|
This tool uses the `spanner-admin` source.
|
||||||
|
{{< /notice >}}
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Here is an example of how to configure the `spanner-create-instance` tool in
|
||||||
|
your `tools.yaml` file:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
sources:
|
||||||
|
my-spanner-admin-source:
|
||||||
|
kind: spanner-admin
|
||||||
|
|
||||||
|
tools:
|
||||||
|
create_my_spanner_instance:
|
||||||
|
kind: spanner-create-instance
|
||||||
|
source: my-spanner-admin-source
|
||||||
|
description: "Creates a Spanner instance."
|
||||||
|
```
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
The `spanner-create-instance` tool has the following parameters:
|
||||||
|
|
||||||
|
| **field** | **type** | **required** | **description** |
|
||||||
|
| --------------- | :------: | :----------: | ------------------------------------------------------------------------------------ |
|
||||||
|
| project | string | true | The Google Cloud project ID. |
|
||||||
|
| instanceId | string | true | The ID of the instance to create. |
|
||||||
|
| displayName | string | true | The display name of the instance. |
|
||||||
|
| config | string | true | The instance configuration (e.g., `regional-us-central1`). |
|
||||||
|
| nodeCount | integer | true | The number of nodes. Mutually exclusive with `processingUnits` (one must be 0). |
|
||||||
|
| processingUnits | integer | true | The number of processing units. Mutually exclusive with `nodeCount` (one must be 0). |
|
||||||
|
| edition | string | false | The edition of the instance (`STANDARD`, `ENTERPRISE`, `ENTERPRISE_PLUS`). |
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
| **field** | **type** | **required** | **description** |
|
||||||
|
| ----------- | :------: | :----------: | ------------------------------------------------------------ |
|
||||||
|
| kind | string | true | Must be `spanner-create-instance`. |
|
||||||
|
| source | string | true | The name of the `spanner-admin` source to use for this tool. |
|
||||||
|
| description | string | false | A description of the tool that is passed to the agent. |
|
||||||
@@ -16,11 +16,7 @@ database. It's compatible with any of the following sources:
|
|||||||
|
|
||||||
- [trino](../../sources/trino.md)
|
- [trino](../../sources/trino.md)
|
||||||
|
|
||||||
The specified SQL statement is executed as a [prepared statement][trino-prepare],
|
The specified SQL statement is executed as a [prepared statement][trino-prepare], and expects parameters in the SQL query to be in the form of placeholders `?`.
|
||||||
and specified parameters will be inserted according to their position: e.g. `$1`
|
|
||||||
will be the first parameter specified, `$2` will be the second parameter, and so
|
|
||||||
on. If template parameters are included, they will be resolved before execution
|
|
||||||
of the prepared statement.
|
|
||||||
|
|
||||||
[trino-prepare]: https://trino.io/docs/current/sql/prepare.html
|
[trino-prepare]: https://trino.io/docs/current/sql/prepare.html
|
||||||
|
|
||||||
@@ -38,8 +34,8 @@ tools:
|
|||||||
source: my-trino-instance
|
source: my-trino-instance
|
||||||
statement: |
|
statement: |
|
||||||
SELECT * FROM hive.sales.orders
|
SELECT * FROM hive.sales.orders
|
||||||
WHERE region = $1
|
WHERE region = ?
|
||||||
AND order_date >= DATE($2)
|
AND order_date >= DATE(?)
|
||||||
LIMIT 10
|
LIMIT 10
|
||||||
description: |
|
description: |
|
||||||
Use this tool to get information for orders in a specific region.
|
Use this tool to get information for orders in a specific region.
|
||||||
|
|||||||
@@ -771,7 +771,7 @@
|
|||||||
},
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"version = \"0.24.0\" # x-release-please-version\n",
|
"version = \"0.25.0\" # x-release-please-version\n",
|
||||||
"! curl -L -o /content/toolbox https://storage.googleapis.com/genai-toolbox/v{version}/linux/amd64/toolbox\n",
|
"! curl -L -o /content/toolbox https://storage.googleapis.com/genai-toolbox/v{version}/linux/amd64/toolbox\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# Make the binary executable\n",
|
"# Make the binary executable\n",
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ In this section, we will download and install the Toolbox binary.
|
|||||||
<!-- {x-release-please-start-version} -->
|
<!-- {x-release-please-start-version} -->
|
||||||
```bash
|
```bash
|
||||||
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
|
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
|
||||||
export VERSION="0.24.0"
|
export VERSION="0.25.0"
|
||||||
curl -O https://storage.googleapis.com/genai-toolbox/v$VERSION/$OS/toolbox
|
curl -O https://storage.googleapis.com/genai-toolbox/v$VERSION/$OS/toolbox
|
||||||
```
|
```
|
||||||
<!-- {x-release-please-end} -->
|
<!-- {x-release-please-end} -->
|
||||||
|
|||||||
@@ -220,7 +220,7 @@
|
|||||||
},
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"version = \"0.24.0\" # x-release-please-version\n",
|
"version = \"0.25.0\" # x-release-please-version\n",
|
||||||
"! curl -O https://storage.googleapis.com/genai-toolbox/v{version}/linux/amd64/toolbox\n",
|
"! curl -O https://storage.googleapis.com/genai-toolbox/v{version}/linux/amd64/toolbox\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# Make the binary executable\n",
|
"# Make the binary executable\n",
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ to use BigQuery, and then run the Toolbox server.
|
|||||||
<!-- {x-release-please-start-version} -->
|
<!-- {x-release-please-start-version} -->
|
||||||
```bash
|
```bash
|
||||||
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
|
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
|
||||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/$OS/toolbox
|
curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/$OS/toolbox
|
||||||
```
|
```
|
||||||
<!-- {x-release-please-end} -->
|
<!-- {x-release-please-end} -->
|
||||||
|
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ In this section, we will download Toolbox, configure our tools in a
|
|||||||
<!-- {x-release-please-start-version} -->
|
<!-- {x-release-please-start-version} -->
|
||||||
```bash
|
```bash
|
||||||
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
|
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
|
||||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/$OS/toolbox
|
curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/$OS/toolbox
|
||||||
```
|
```
|
||||||
<!-- {x-release-please-end} -->
|
<!-- {x-release-please-end} -->
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ In this section, we will download Toolbox and run the Toolbox server.
|
|||||||
<!-- {x-release-please-start-version} -->
|
<!-- {x-release-please-start-version} -->
|
||||||
```bash
|
```bash
|
||||||
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
|
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
|
||||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/$OS/toolbox
|
curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/$OS/toolbox
|
||||||
```
|
```
|
||||||
<!-- {x-release-please-end} -->
|
<!-- {x-release-please-end} -->
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ In this section, we will download Toolbox and run the Toolbox server.
|
|||||||
<!-- {x-release-please-start-version} -->
|
<!-- {x-release-please-start-version} -->
|
||||||
```bash
|
```bash
|
||||||
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
|
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
|
||||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/$OS/toolbox
|
curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/$OS/toolbox
|
||||||
```
|
```
|
||||||
<!-- {x-release-please-end} -->
|
<!-- {x-release-please-end} -->
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ In this section, we will download Toolbox and run the Toolbox server.
|
|||||||
<!-- {x-release-please-start-version} -->
|
<!-- {x-release-please-start-version} -->
|
||||||
```bash
|
```bash
|
||||||
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
|
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
|
||||||
curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/$OS/toolbox
|
curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/$OS/toolbox
|
||||||
```
|
```
|
||||||
<!-- {x-release-please-end} -->
|
<!-- {x-release-please-end} -->
|
||||||
|
|
||||||
|
|||||||
161
docs/en/samples/snowflake/_index.md
Normal file
161
docs/en/samples/snowflake/_index.md
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
---
|
||||||
|
title: "Snowflake"
|
||||||
|
type: docs
|
||||||
|
weight: 2
|
||||||
|
description: >
|
||||||
|
How to get started running Toolbox with MCP Inspector and Snowflake as the source.
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
[Model Context Protocol](https://modelcontextprotocol.io) is an open protocol
|
||||||
|
that standardizes how applications provide context to LLMs. Check out this page
|
||||||
|
on how to [connect to Toolbox via MCP](../../how-to/connect_via_mcp.md).
|
||||||
|
|
||||||
|
## Before you begin
|
||||||
|
|
||||||
|
This guide assumes you have already done the following:
|
||||||
|
|
||||||
|
1. [Create a Snowflake account](https://signup.snowflake.com/).
|
||||||
|
1. Connect to the instance using [SnowSQL](https://docs.snowflake.com/en/user-guide/snowsql), or any other Snowflake client.
|
||||||
|
|
||||||
|
## Step 1: Set up your environment
|
||||||
|
|
||||||
|
Copy the environment template and update it with your Snowflake credentials:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp examples/snowflake-env.sh my-snowflake-env.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Edit `my-snowflake-env.sh` with your actual Snowflake connection details:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export SNOWFLAKE_ACCOUNT="your-account-identifier"
|
||||||
|
export SNOWFLAKE_USER="your-username"
|
||||||
|
export SNOWFLAKE_PASSWORD="your-password"
|
||||||
|
export SNOWFLAKE_DATABASE="your-database"
|
||||||
|
export SNOWFLAKE_SCHEMA="your-schema"
|
||||||
|
export SNOWFLAKE_WAREHOUSE="COMPUTE_WH"
|
||||||
|
export SNOWFLAKE_ROLE="ACCOUNTADMIN"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 2: Install Toolbox
|
||||||
|
|
||||||
|
In this section, we will download and install the Toolbox binary.
|
||||||
|
|
||||||
|
1. Download the latest version of Toolbox as a binary:
|
||||||
|
|
||||||
|
{{< notice tip >}}
|
||||||
|
Select the
|
||||||
|
[correct binary](https://github.com/googleapis/genai-toolbox/releases)
|
||||||
|
corresponding to your OS and CPU architecture.
|
||||||
|
{{< /notice >}}
|
||||||
|
<!-- {x-release-please-start-version} -->
|
||||||
|
```bash
|
||||||
|
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
|
||||||
|
export VERSION="0.10.0"
|
||||||
|
curl -O https://storage.googleapis.com/genai-toolbox/v$VERSION/$OS/toolbox
|
||||||
|
```
|
||||||
|
<!-- {x-release-please-end} -->
|
||||||
|
|
||||||
|
1. Make the binary executable:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod +x toolbox
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 3: Configure the tools
|
||||||
|
|
||||||
|
You have two options:
|
||||||
|
|
||||||
|
#### Option A: Use the prebuilt configuration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./toolbox --prebuilt snowflake
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Option B: Use the custom configuration
|
||||||
|
|
||||||
|
Create a `tools.yaml` file and add the following content. You must replace the placeholders with your actual Snowflake configuration.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
sources:
|
||||||
|
snowflake-source:
|
||||||
|
kind: snowflake
|
||||||
|
account: ${SNOWFLAKE_ACCOUNT}
|
||||||
|
user: ${SNOWFLAKE_USER}
|
||||||
|
password: ${SNOWFLAKE_PASSWORD}
|
||||||
|
database: ${SNOWFLAKE_DATABASE}
|
||||||
|
schema: ${SNOWFLAKE_SCHEMA}
|
||||||
|
warehouse: ${SNOWFLAKE_WAREHOUSE}
|
||||||
|
role: ${SNOWFLAKE_ROLE}
|
||||||
|
|
||||||
|
tools:
|
||||||
|
execute_sql:
|
||||||
|
kind: snowflake-execute-sql
|
||||||
|
source: snowflake-source
|
||||||
|
description: Use this tool to execute SQL.
|
||||||
|
|
||||||
|
list_tables:
|
||||||
|
kind: snowflake-sql
|
||||||
|
source: snowflake-source
|
||||||
|
description: "Lists detailed schema information for user-created tables."
|
||||||
|
statement: |
|
||||||
|
SELECT table_name, table_type
|
||||||
|
FROM information_schema.tables
|
||||||
|
WHERE table_schema = current_schema()
|
||||||
|
ORDER BY table_name;
|
||||||
|
```
|
||||||
|
|
||||||
|
For more info on tools, check out the
|
||||||
|
[Tools](../../resources/tools/) section.
|
||||||
|
|
||||||
|
## Step 4: Run the Toolbox server
|
||||||
|
|
||||||
|
Run the Toolbox server, pointing to the `tools.yaml` file created earlier:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./toolbox --tools-file "tools.yaml"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 5: Connect to MCP Inspector
|
||||||
|
|
||||||
|
1. Run the MCP Inspector:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx @modelcontextprotocol/inspector
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Type `y` when it asks to install the inspector package.
|
||||||
|
|
||||||
|
1. It should show the following when the MCP Inspector is up and running (please take note of `<YOUR_SESSION_TOKEN>`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
Starting MCP inspector...
|
||||||
|
⚙️ Proxy server listening on localhost:6277
|
||||||
|
🔑 Session token: <YOUR_SESSION_TOKEN>
|
||||||
|
Use this token to authenticate requests or set DANGEROUSLY_OMIT_AUTH=true to disable auth
|
||||||
|
|
||||||
|
🚀 MCP Inspector is up and running at:
|
||||||
|
http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=<YOUR_SESSION_TOKEN>
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Open the above link in your browser.
|
||||||
|
|
||||||
|
1. For `Transport Type`, select `Streamable HTTP`.
|
||||||
|
|
||||||
|
1. For `URL`, type in `http://127.0.0.1:5000/mcp`.
|
||||||
|
|
||||||
|
1. For `Configuration` -> `Proxy Session Token`, make sure `<YOUR_SESSION_TOKEN>` is present.
|
||||||
|
|
||||||
|
1. Click Connect.
|
||||||
|
|
||||||
|
1. Select `List Tools`, you will see a list of tools configured in `tools.yaml`.
|
||||||
|
|
||||||
|
1. Test out your tools here!
|
||||||
|
|
||||||
|
## What's next
|
||||||
|
|
||||||
|
- Learn more about [MCP Inspector](../../how-to/connect_via_mcp.md).
|
||||||
|
- Learn more about [Toolbox Resources](../../resources/).
|
||||||
|
- Learn more about [Toolbox How-to guides](../../how-to/).
|
||||||
18
docs/en/samples/snowflake/runme.py
Normal file
18
docs/en/samples/snowflake/runme.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import asyncio
|
||||||
|
from toolbox_core import ToolboxClient
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
# Replace with the actual URL where your Toolbox service is running
|
||||||
|
async with ToolboxClient("http://127.0.0.1:5000") as toolbox:
|
||||||
|
tool = await toolbox.load_tool("execute_sql")
|
||||||
|
result = await tool("SELECT 1")
|
||||||
|
print(result)
|
||||||
|
|
||||||
|
tool = await toolbox.load_tool("list_tables")
|
||||||
|
result = await tool(table_names="DIM_DATE")
|
||||||
|
print(result)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
68
docs/en/samples/snowflake/snowflake-config.yaml
Normal file
68
docs/en/samples/snowflake/snowflake-config.yaml
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# Copyright 2026 Google LLC
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
sources:
|
||||||
|
my-snowflake-db:
|
||||||
|
kind: snowflake
|
||||||
|
account: ${SNOWFLAKE_ACCOUNT}
|
||||||
|
user: ${SNOWFLAKE_USER}
|
||||||
|
password: ${SNOWFLAKE_PASSWORD}
|
||||||
|
database: ${SNOWFLAKE_DATABASE}
|
||||||
|
schema: ${SNOWFLAKE_SCHEMA}
|
||||||
|
warehouse: ${SNOWFLAKE_WAREHOUSE} # Optional, defaults to COMPUTE_WH if not set
|
||||||
|
role: ${SNOWFLAKE_ROLE} # Optional, defaults to ACCOUNTADMIN if not set
|
||||||
|
|
||||||
|
tools:
|
||||||
|
execute_sql:
|
||||||
|
kind: snowflake-execute-sql
|
||||||
|
source: my-snowflake-db
|
||||||
|
description: Execute arbitrary SQL statements on Snowflake
|
||||||
|
|
||||||
|
get_customer_orders:
|
||||||
|
kind: snowflake-sql
|
||||||
|
source: my-snowflake-db
|
||||||
|
description: Get orders for a specific customer
|
||||||
|
statement: |
|
||||||
|
SELECT o.order_id, o.order_date, o.total_amount, o.status
|
||||||
|
FROM orders o
|
||||||
|
WHERE o.customer_id = $1
|
||||||
|
ORDER BY o.order_date DESC
|
||||||
|
parameters:
|
||||||
|
- name: customer_id
|
||||||
|
type: string
|
||||||
|
description: The customer ID to look up orders for
|
||||||
|
|
||||||
|
daily_sales_report:
|
||||||
|
kind: snowflake-sql
|
||||||
|
source: my-snowflake-db
|
||||||
|
description: Generate daily sales report for a specific date
|
||||||
|
statement: |
|
||||||
|
SELECT
|
||||||
|
DATE(order_date) as sales_date,
|
||||||
|
COUNT(*) as total_orders,
|
||||||
|
SUM(total_amount) as total_revenue,
|
||||||
|
AVG(total_amount) as avg_order_value
|
||||||
|
FROM orders
|
||||||
|
WHERE DATE(order_date) = $1
|
||||||
|
GROUP BY DATE(order_date)
|
||||||
|
parameters:
|
||||||
|
- name: report_date
|
||||||
|
type: string
|
||||||
|
description: The date to generate report for (YYYY-MM-DD format)
|
||||||
|
|
||||||
|
toolsets:
|
||||||
|
snowflake-analytics:
|
||||||
|
- execute_sql
|
||||||
|
- get_customer_orders
|
||||||
|
- daily_sales_report
|
||||||
28
docs/en/samples/snowflake/snowflake-env.sh
Normal file
28
docs/en/samples/snowflake/snowflake-env.sh
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Snowflake Connection Configuration
|
||||||
|
# Copy this file to snowflake-env.sh and update with your actual values
|
||||||
|
# Then source it before running the toolbox: source snowflake-env.sh
|
||||||
|
|
||||||
|
# Required environment variables
|
||||||
|
export SNOWFLAKE_ACCOUNT="your-account-identifier" # e.g., "xy12345.snowflakecomputing.com"
|
||||||
|
export SNOWFLAKE_USER="your-username" # Your Snowflake username
|
||||||
|
export SNOWFLAKE_PASSWORD="your-password" # Your Snowflake password
|
||||||
|
export SNOWFLAKE_DATABASE="your-database" # Database name
|
||||||
|
export SNOWFLAKE_SCHEMA="your-schema" # Schema name (usually "PUBLIC")
|
||||||
|
|
||||||
|
# Optional environment variables (will use defaults if not set)
|
||||||
|
export SNOWFLAKE_WAREHOUSE="COMPUTE_WH" # Warehouse name (default: COMPUTE_WH)
|
||||||
|
export SNOWFLAKE_ROLE="ACCOUNTADMIN" # Role name (default: ACCOUNTADMIN)
|
||||||
|
|
||||||
|
echo "Snowflake environment variables have been set!"
|
||||||
|
echo "Account: $SNOWFLAKE_ACCOUNT"
|
||||||
|
echo "User: $SNOWFLAKE_USER"
|
||||||
|
echo "Database: $SNOWFLAKE_DATABASE"
|
||||||
|
echo "Schema: $SNOWFLAKE_SCHEMA"
|
||||||
|
echo "Warehouse: $SNOWFLAKE_WAREHOUSE"
|
||||||
|
echo "Role: $SNOWFLAKE_ROLE"
|
||||||
|
echo ""
|
||||||
|
echo "You can now run the toolbox with:"
|
||||||
|
echo " ./toolbox --prebuilt snowflake # Use prebuilt configuration"
|
||||||
|
echo " ./toolbox --tools-file docs/en/samples/snowflake/snowflake-config.yaml # Use custom configuration"
|
||||||
56
docs/en/samples/snowflake/test-snowflake.sh
Normal file
56
docs/en/samples/snowflake/test-snowflake.sh
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Test script to demonstrate Snowflake configuration with environment variables
|
||||||
|
# This script shows how to set up and test the Snowflake toolbox configuration
|
||||||
|
|
||||||
|
echo "=== Testing Snowflake Configuration ==="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Set up test environment variables (replace with your actual values)
|
||||||
|
echo "Setting up test environment variables..."
|
||||||
|
export SNOWFLAKE_ACCOUNT="test-account"
|
||||||
|
export SNOWFLAKE_USER="test-user"
|
||||||
|
export SNOWFLAKE_PASSWORD="test-password"
|
||||||
|
export SNOWFLAKE_DATABASE="test-database"
|
||||||
|
export SNOWFLAKE_SCHEMA="test-schema"
|
||||||
|
export SNOWFLAKE_WAREHOUSE="COMPUTE_WH"
|
||||||
|
export SNOWFLAKE_ROLE="ACCOUNTADMIN"
|
||||||
|
|
||||||
|
echo "Environment variables set:"
|
||||||
|
echo " SNOWFLAKE_ACCOUNT: $SNOWFLAKE_ACCOUNT"
|
||||||
|
echo " SNOWFLAKE_USER: $SNOWFLAKE_USER"
|
||||||
|
echo " SNOWFLAKE_DATABASE: $SNOWFLAKE_DATABASE"
|
||||||
|
echo " SNOWFLAKE_SCHEMA: $SNOWFLAKE_SCHEMA"
|
||||||
|
echo " SNOWFLAKE_WAREHOUSE: $SNOWFLAKE_WAREHOUSE"
|
||||||
|
echo " SNOWFLAKE_ROLE: $SNOWFLAKE_ROLE"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=== Testing Prebuilt Configuration ==="
|
||||||
|
echo "This will attempt to initialize with the prebuilt Snowflake configuration:"
|
||||||
|
echo "Command: ./toolbox --prebuilt snowflake --stdio"
|
||||||
|
echo ""
|
||||||
|
echo "Expected result: Connection failure due to test credentials (this is normal)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Test the prebuilt configuration (this will fail with test credentials, which is expected)
|
||||||
|
timeout 5s ./toolbox --prebuilt snowflake --stdio 2>&1 | head -5
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Testing Custom Configuration ==="
|
||||||
|
echo "This will attempt to initialize with the custom Snowflake configuration:"
|
||||||
|
echo "Command: ./toolbox --tools-file docs/en/samples/snowflake/snowflake-config.yaml --stdio"
|
||||||
|
echo ""
|
||||||
|
echo "Expected result: Connection failure due to test credentials (this is normal)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Test the custom configuration (this will fail with test credentials, which is expected)
|
||||||
|
timeout 5s ./toolbox --tools-file docs/en/samples/snowflake/snowflake-config.yaml --stdio 2>&1 | head -5
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Instructions for Real Usage ==="
|
||||||
|
echo "1. Copy docs/en/samples/snowflake/snowflake-env.sh to your own file"
|
||||||
|
echo "2. Edit it with your actual Snowflake credentials"
|
||||||
|
echo "3. Source the file: source your-snowflake-env.sh"
|
||||||
|
echo "4. Run: ./toolbox --prebuilt snowflake"
|
||||||
|
echo ""
|
||||||
|
echo "For more information, see docs/en/samples/snowflake"
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mcp-toolbox-for-databases",
|
"name": "mcp-toolbox-for-databases",
|
||||||
"version": "0.24.0",
|
"version": "0.25.0",
|
||||||
"description": "MCP Toolbox for Databases is an open-source MCP server for more than 30 different datasources.",
|
"description": "MCP Toolbox for Databases is an open-source MCP server for more than 30 different datasources.",
|
||||||
"contextFileName": "MCP-TOOLBOX-EXTENSION.md"
|
"contextFileName": "MCP-TOOLBOX-EXTENSION.md"
|
||||||
}
|
}
|
||||||
41
go.mod
41
go.mod
@@ -37,12 +37,14 @@ require (
|
|||||||
github.com/google/go-cmp v0.7.0
|
github.com/google/go-cmp v0.7.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/jackc/pgx/v5 v5.7.6
|
github.com/jackc/pgx/v5 v5.7.6
|
||||||
|
github.com/jmoiron/sqlx v1.4.0
|
||||||
github.com/looker-open-source/sdk-codegen/go v0.25.21
|
github.com/looker-open-source/sdk-codegen/go v0.25.21
|
||||||
github.com/microsoft/go-mssqldb v1.9.3
|
github.com/microsoft/go-mssqldb v1.9.3
|
||||||
github.com/nakagami/firebirdsql v0.9.15
|
github.com/nakagami/firebirdsql v0.9.15
|
||||||
github.com/neo4j/neo4j-go-driver/v5 v5.28.4
|
github.com/neo4j/neo4j-go-driver/v5 v5.28.4
|
||||||
github.com/redis/go-redis/v9 v9.17.2
|
github.com/redis/go-redis/v9 v9.17.2
|
||||||
github.com/sijms/go-ora/v2 v2.9.0
|
github.com/sijms/go-ora/v2 v2.9.0
|
||||||
|
github.com/snowflakedb/gosnowflake v1.18.1
|
||||||
github.com/spf13/cobra v1.10.1
|
github.com/spf13/cobra v1.10.1
|
||||||
github.com/thlib/go-timezone-local v0.0.7
|
github.com/thlib/go-timezone-local v0.0.7
|
||||||
github.com/trinodb/trino-go-client v0.330.0
|
github.com/trinodb/trino-go-client v0.330.0
|
||||||
@@ -88,13 +90,40 @@ require (
|
|||||||
cloud.google.com/go/monitoring v1.24.3 // indirect
|
cloud.google.com/go/monitoring v1.24.3 // indirect
|
||||||
cloud.google.com/go/trace v1.11.7 // indirect
|
cloud.google.com/go/trace v1.11.7 // indirect
|
||||||
filippo.io/edwards25519 v1.1.0 // indirect
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
|
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
|
||||||
|
github.com/99designs/keyring v1.2.2 // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0 // indirect
|
||||||
|
github.com/BurntSushi/toml v1.4.0 // indirect
|
||||||
github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.3 // indirect
|
github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.3 // indirect
|
||||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect
|
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect
|
||||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 // indirect
|
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 // indirect
|
||||||
github.com/PuerkitoBio/goquery v1.10.3 // indirect
|
github.com/PuerkitoBio/goquery v1.10.3 // indirect
|
||||||
github.com/VictoriaMetrics/easyproto v0.1.4 // indirect
|
github.com/VictoriaMetrics/easyproto v0.1.4 // indirect
|
||||||
github.com/ajg/form v1.5.1 // indirect
|
github.com/ajg/form v1.5.1 // indirect
|
||||||
|
github.com/apache/arrow-go/v18 v18.4.0 // indirect
|
||||||
github.com/apache/arrow/go/v15 v15.0.2 // indirect
|
github.com/apache/arrow/go/v15 v15.0.2 // indirect
|
||||||
|
github.com/apache/thrift v0.22.0 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.39.0 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/config v1.31.8 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/credentials v1.18.12 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.7 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.15 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.7 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.7 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.7 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.7 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.7 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.7 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.88.1 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sso v1.29.3 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.4 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.38.4 // indirect
|
||||||
|
github.com/aws/smithy-go v1.23.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
|
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
|
||||||
github.com/couchbase/gocbcore/v10 v10.8.1 // indirect
|
github.com/couchbase/gocbcore/v10 v10.8.1 // indirect
|
||||||
@@ -102,8 +131,10 @@ require (
|
|||||||
github.com/couchbase/goprotostellar v1.0.2 // indirect
|
github.com/couchbase/goprotostellar v1.0.2 // indirect
|
||||||
github.com/couchbase/tools-common/errors v1.0.0 // indirect
|
github.com/couchbase/tools-common/errors v1.0.0 // indirect
|
||||||
github.com/couchbaselabs/gocbconnstr/v2 v2.0.0 // indirect
|
github.com/couchbaselabs/gocbconnstr/v2 v2.0.0 // indirect
|
||||||
|
github.com/danieljoos/wincred v1.2.2 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/dvsekhvalnov/jose2go v1.7.0 // indirect
|
||||||
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
|
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
|
||||||
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
|
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
|
||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
@@ -115,7 +146,9 @@ require (
|
|||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/goccy/go-json v0.10.5 // indirect
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
|
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
|
||||||
github.com/godror/knownpb v0.3.0 // indirect
|
github.com/godror/knownpb v0.3.0 // indirect
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
|
||||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||||
@@ -127,6 +160,7 @@ require (
|
|||||||
github.com/gorilla/websocket v1.5.3 // indirect
|
github.com/gorilla/websocket v1.5.3 // indirect
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
|
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
|
||||||
|
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
|
||||||
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
@@ -140,19 +174,25 @@ require (
|
|||||||
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
|
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||||
|
github.com/klauspost/asmfmt v1.3.2 // indirect
|
||||||
github.com/klauspost/compress v1.18.0 // indirect
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.11 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.11 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 // indirect
|
||||||
|
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||||
|
github.com/mtibben/percent v0.2.1 // indirect
|
||||||
github.com/nakagami/chacha20 v0.1.0 // indirect
|
github.com/nakagami/chacha20 v0.1.0 // indirect
|
||||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||||
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
|
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||||
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
github.com/spf13/pflag v1.0.9 // indirect
|
github.com/spf13/pflag v1.0.9 // indirect
|
||||||
github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
|
github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||||
@@ -182,6 +222,7 @@ require (
|
|||||||
golang.org/x/sync v0.18.0 // indirect
|
golang.org/x/sync v0.18.0 // indirect
|
||||||
golang.org/x/sys v0.38.0 // indirect
|
golang.org/x/sys v0.38.0 // indirect
|
||||||
golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 // indirect
|
golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 // indirect
|
||||||
|
golang.org/x/term v0.37.0 // indirect
|
||||||
golang.org/x/text v0.31.0 // indirect
|
golang.org/x/text v0.31.0 // indirect
|
||||||
golang.org/x/time v0.14.0 // indirect
|
golang.org/x/time v0.14.0 // indirect
|
||||||
golang.org/x/tools v0.38.0 // indirect
|
golang.org/x/tools v0.38.0 // indirect
|
||||||
|
|||||||
40
go.sum
40
go.sum
@@ -643,6 +643,10 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
|||||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8=
|
gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8=
|
||||||
git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc=
|
git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc=
|
||||||
|
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=
|
||||||
|
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=
|
||||||
|
github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0=
|
||||||
|
github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U=
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4=
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4=
|
||||||
@@ -653,11 +657,15 @@ github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1 h1:Wgf5rZb
|
|||||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1/go.mod h1:xxCBG/f/4Vbmh2XQJBsOmNdxWUY5j/s27jujKPbQf14=
|
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1/go.mod h1:xxCBG/f/4Vbmh2XQJBsOmNdxWUY5j/s27jujKPbQf14=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 h1:bFWuoEKg+gImo7pvkiQEFAc8ocibADgXeiLAxWhWmkI=
|
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 h1:bFWuoEKg+gImo7pvkiQEFAc8ocibADgXeiLAxWhWmkI=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h1:Vih/3yc6yac2JzU4hzpaDupBJP0Flaia9rXXrU8xyww=
|
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h1:Vih/3yc6yac2JzU4hzpaDupBJP0Flaia9rXXrU8xyww=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0 h1:u/LLAOFgsMv7HmNL4Qufg58y+qElGOt5qv0z1mURkRY=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0/go.mod h1:2e8rMJtl2+2j+HXbTBwnyGpm5Nou7KhvSfxOq8JpTag=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
|
||||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
||||||
|
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/ClickHouse/ch-go v0.68.0 h1:zd2VD8l2aVYnXFRyhTyKCrxvhSz1AaY4wBUXu/f0GiU=
|
github.com/ClickHouse/ch-go v0.68.0 h1:zd2VD8l2aVYnXFRyhTyKCrxvhSz1AaY4wBUXu/f0GiU=
|
||||||
github.com/ClickHouse/ch-go v0.68.0/go.mod h1:C89Fsm7oyck9hr6rRo5gqqiVtaIY6AjdD0WFMyNRQ5s=
|
github.com/ClickHouse/ch-go v0.68.0/go.mod h1:C89Fsm7oyck9hr6rRo5gqqiVtaIY6AjdD0WFMyNRQ5s=
|
||||||
@@ -701,6 +709,8 @@ github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUS
|
|||||||
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
||||||
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||||
|
github.com/apache/arrow-go/v18 v18.4.0 h1:/RvkGqH517iY8bZKc4FD5/kkdwXJGjxf28JIXbJ/oB0=
|
||||||
|
github.com/apache/arrow-go/v18 v18.4.0/go.mod h1:Aawvwhj8x2jURIzD9Moy72cF0FyJXOpkYpdmGRHcw14=
|
||||||
github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0=
|
github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0=
|
||||||
github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI=
|
github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI=
|
||||||
github.com/apache/arrow/go/v15 v15.0.2 h1:60IliRbiyTWCWjERBCkO1W4Qun9svcYoZrSLcyOsMLE=
|
github.com/apache/arrow/go/v15 v15.0.2 h1:60IliRbiyTWCWjERBCkO1W4Qun9svcYoZrSLcyOsMLE=
|
||||||
@@ -708,6 +718,8 @@ github.com/apache/arrow/go/v15 v15.0.2/go.mod h1:DGXsR3ajT524njufqf95822i+KTh+ye
|
|||||||
github.com/apache/cassandra-gocql-driver/v2 v2.0.0 h1:Omnzb1Z/P90Dr2TbVNu54ICQL7TKVIIsJO231w484HU=
|
github.com/apache/cassandra-gocql-driver/v2 v2.0.0 h1:Omnzb1Z/P90Dr2TbVNu54ICQL7TKVIIsJO231w484HU=
|
||||||
github.com/apache/cassandra-gocql-driver/v2 v2.0.0/go.mod h1:QH/asJjB3mHvY6Dot6ZKMMpTcOrWJ8i9GhsvG1g0PK4=
|
github.com/apache/cassandra-gocql-driver/v2 v2.0.0/go.mod h1:QH/asJjB3mHvY6Dot6ZKMMpTcOrWJ8i9GhsvG1g0PK4=
|
||||||
github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU=
|
github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU=
|
||||||
|
github.com/apache/thrift v0.22.0 h1:r7mTJdj51TMDe6RtcmNdQxgn9XcyfGDOzegMDRg47uc=
|
||||||
|
github.com/apache/thrift v0.22.0/go.mod h1:1e7J/O1Ae6ZQMTYdy9xa3w9k+XHWPfRvdPyJeynQ+/g=
|
||||||
github.com/aws/aws-sdk-go v1.55.8 h1:JRmEUbU52aJQZ2AjX4q4Wu7t4uZjOu71uyNmaWlUkJQ=
|
github.com/aws/aws-sdk-go v1.55.8 h1:JRmEUbU52aJQZ2AjX4q4Wu7t4uZjOu71uyNmaWlUkJQ=
|
||||||
github.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPPvQkUtXk=
|
github.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPPvQkUtXk=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.39.0 h1:xm5WV/2L4emMRmMjHFykqiA4M/ra0DJVSWUkDyBjbg4=
|
github.com/aws/aws-sdk-go-v2 v1.39.0 h1:xm5WV/2L4emMRmMjHFykqiA4M/ra0DJVSWUkDyBjbg4=
|
||||||
@@ -720,6 +732,8 @@ github.com/aws/aws-sdk-go-v2/credentials v1.18.12 h1:zmc9e1q90wMn8wQbjryy8IwA6Q4
|
|||||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.12/go.mod h1:3VzdRDR5u3sSJRI4kYcOSIBbeYsgtVk7dG5R/U6qLWY=
|
github.com/aws/aws-sdk-go-v2/credentials v1.18.12/go.mod h1:3VzdRDR5u3sSJRI4kYcOSIBbeYsgtVk7dG5R/U6qLWY=
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.7 h1:Is2tPmieqGS2edBnmOJIbdvOA6Op+rRpaYR60iBAwXM=
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.7 h1:Is2tPmieqGS2edBnmOJIbdvOA6Op+rRpaYR60iBAwXM=
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.7/go.mod h1:F1i5V5421EGci570yABvpIXgRIBPb5JM+lSkHF6Dq5w=
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.7/go.mod h1:F1i5V5421EGci570yABvpIXgRIBPb5JM+lSkHF6Dq5w=
|
||||||
|
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.15 h1:7Zwtt/lP3KNRkeZre7soMELMGNoBrutx8nobg1jKWmo=
|
||||||
|
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.15/go.mod h1:436h2adoHb57yd+8W+gYPrrA9U/R/SuAuOO42Ushzhw=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.7 h1:UCxq0X9O3xrlENdKf1r9eRJoKz/b0AfGkpp3a7FPlhg=
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.7 h1:UCxq0X9O3xrlENdKf1r9eRJoKz/b0AfGkpp3a7FPlhg=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.7/go.mod h1:rHRoJUNUASj5Z/0eqI4w32vKvC7atoWR0jC+IkmVH8k=
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.7/go.mod h1:rHRoJUNUASj5Z/0eqI4w32vKvC7atoWR0jC+IkmVH8k=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.7 h1:Y6DTZUn7ZUC4th9FMBbo8LVE+1fyq3ofw+tRwkUd3PY=
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.7 h1:Y6DTZUn7ZUC4th9FMBbo8LVE+1fyq3ofw+tRwkUd3PY=
|
||||||
@@ -804,6 +818,8 @@ github.com/couchbaselabs/gocbconnstr/v2 v2.0.0 h1:HU9DlAYYWR69jQnLN6cpg0fh0hxW/8
|
|||||||
github.com/couchbaselabs/gocbconnstr/v2 v2.0.0/go.mod h1:o7T431UOfFVHDNvMBUmUxpHnhivwv7BziUao/nMl81E=
|
github.com/couchbaselabs/gocbconnstr/v2 v2.0.0/go.mod h1:o7T431UOfFVHDNvMBUmUxpHnhivwv7BziUao/nMl81E=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/danieljoos/wincred v1.2.2 h1:774zMFJrqaeYCK2W57BgAem/MLi6mtSE47MB6BOJ0i0=
|
||||||
|
github.com/danieljoos/wincred v1.2.2/go.mod h1:w7w4Utbrz8lqeMbDAK0lkNJUv5sAOkFi7nd/ogr0Uh8=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
@@ -822,6 +838,8 @@ github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3
|
|||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
|
github.com/dvsekhvalnov/jose2go v1.7.0 h1:bnQc8+GMnidJZA8zc6lLEAb4xNrIqHwO+9TzqvtQZPo=
|
||||||
|
github.com/dvsekhvalnov/jose2go v1.7.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU=
|
||||||
github.com/elastic/elastic-transport-go/v8 v8.8.0 h1:7k1Ua+qluFr6p1jfJjGDl97ssJS/P7cHNInzfxgBQAo=
|
github.com/elastic/elastic-transport-go/v8 v8.8.0 h1:7k1Ua+qluFr6p1jfJjGDl97ssJS/P7cHNInzfxgBQAo=
|
||||||
github.com/elastic/elastic-transport-go/v8 v8.8.0/go.mod h1:YLHer5cj0csTzNFXoNQ8qhtGY1GTvSqPnKWKaqQE3Hk=
|
github.com/elastic/elastic-transport-go/v8 v8.8.0/go.mod h1:YLHer5cj0csTzNFXoNQ8qhtGY1GTvSqPnKWKaqQE3Hk=
|
||||||
github.com/elastic/go-elasticsearch/v9 v9.2.0 h1:COeL/g20+ixnUbffe4Wfbu88emrHjAq/LhVfmrjqRQs=
|
github.com/elastic/go-elasticsearch/v9 v9.2.0 h1:COeL/g20+ixnUbffe4Wfbu88emrHjAq/LhVfmrjqRQs=
|
||||||
@@ -905,6 +923,7 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
|
|||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688=
|
github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688=
|
||||||
github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU=
|
github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU=
|
||||||
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
@@ -915,6 +934,8 @@ github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
|||||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||||
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
|
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0=
|
||||||
|
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
|
||||||
github.com/godror/godror v0.49.6 h1:ts4ZGw8uLJ42e1D7aXmVuSrld0/lzUzmIUjuUuQOgGM=
|
github.com/godror/godror v0.49.6 h1:ts4ZGw8uLJ42e1D7aXmVuSrld0/lzUzmIUjuUuQOgGM=
|
||||||
github.com/godror/godror v0.49.6/go.mod h1:kTMcxZzRw73RT5kn9v3JkBK4kHI6dqowHotqV72ebU8=
|
github.com/godror/godror v0.49.6/go.mod h1:kTMcxZzRw73RT5kn9v3JkBK4kHI6dqowHotqV72ebU8=
|
||||||
github.com/godror/knownpb v0.3.0 h1:+caUdy8hTtl7X05aPl3tdL540TvCcaQA6woZQroLZMw=
|
github.com/godror/knownpb v0.3.0 h1:+caUdy8hTtl7X05aPl3tdL540TvCcaQA6woZQroLZMw=
|
||||||
@@ -1066,6 +1087,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4Zs
|
|||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
|
||||||
|
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
|
||||||
|
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
|
||||||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
@@ -1110,6 +1133,8 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6
|
|||||||
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
|
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
|
||||||
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
|
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
|
||||||
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
||||||
|
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
||||||
|
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
@@ -1121,6 +1146,7 @@ github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALr
|
|||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4=
|
||||||
github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE=
|
github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE=
|
||||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||||
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||||
@@ -1144,6 +1170,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
|
|||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/looker-open-source/sdk-codegen/go v0.25.21 h1:nlZ1nz22SKluBNkzplrMHBPEVgJO3zVLF6aAws1rrRA=
|
github.com/looker-open-source/sdk-codegen/go v0.25.21 h1:nlZ1nz22SKluBNkzplrMHBPEVgJO3zVLF6aAws1rrRA=
|
||||||
github.com/looker-open-source/sdk-codegen/go v0.25.21/go.mod h1:Br1ntSiruDJ/4nYNjpYyWyCbqJ7+GQceWbIgn0hYims=
|
github.com/looker-open-source/sdk-codegen/go v0.25.21/go.mod h1:Br1ntSiruDJ/4nYNjpYyWyCbqJ7+GQceWbIgn0hYims=
|
||||||
github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
|
github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
|
||||||
@@ -1156,9 +1184,13 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
|
|||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/microsoft/go-mssqldb v1.9.3 h1:hy4p+LDC8LIGvI3JATnLVmBOLMJbmn5X400mr5j0lPs=
|
github.com/microsoft/go-mssqldb v1.9.3 h1:hy4p+LDC8LIGvI3JATnLVmBOLMJbmn5X400mr5j0lPs=
|
||||||
github.com/microsoft/go-mssqldb v1.9.3/go.mod h1:GBbW9ASTiDC+mpgWDGKdm3FnFLTUsLYN3iFL90lQ+PA=
|
github.com/microsoft/go-mssqldb v1.9.3/go.mod h1:GBbW9ASTiDC+mpgWDGKdm3FnFLTUsLYN3iFL90lQ+PA=
|
||||||
|
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs=
|
||||||
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY=
|
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY=
|
||||||
|
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI=
|
||||||
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE=
|
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE=
|
||||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||||
@@ -1174,6 +1206,8 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY
|
|||||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||||
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
|
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
|
||||||
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||||
|
github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
|
||||||
|
github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
|
||||||
github.com/nakagami/chacha20 v0.1.0 h1:2fbf5KeVUw7oRpAe6/A7DqvBJLYYu0ka5WstFbnkEVo=
|
github.com/nakagami/chacha20 v0.1.0 h1:2fbf5KeVUw7oRpAe6/A7DqvBJLYYu0ka5WstFbnkEVo=
|
||||||
github.com/nakagami/chacha20 v0.1.0/go.mod h1:xpoujepNFA7MvYLvX5xKHzlOHimDrLI9Ll8zfOJ0l2E=
|
github.com/nakagami/chacha20 v0.1.0/go.mod h1:xpoujepNFA7MvYLvX5xKHzlOHimDrLI9Ll8zfOJ0l2E=
|
||||||
github.com/nakagami/firebirdsql v0.9.15 h1:Mf05jaFI8+kjy6sBstsAu76zOkJ44AGd6cpApWNrp/0=
|
github.com/nakagami/firebirdsql v0.9.15 h1:Mf05jaFI8+kjy6sBstsAu76zOkJ44AGd6cpApWNrp/0=
|
||||||
@@ -1182,6 +1216,7 @@ github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdh
|
|||||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||||
github.com/neo4j/neo4j-go-driver/v5 v5.28.4 h1:7toxehVcYkZbyxV4W3Ib9VcnyRBQPucF+VwNNmtSXi4=
|
github.com/neo4j/neo4j-go-driver/v5 v5.28.4 h1:7toxehVcYkZbyxV4W3Ib9VcnyRBQPucF+VwNNmtSXi4=
|
||||||
github.com/neo4j/neo4j-go-driver/v5 v5.28.4/go.mod h1:Vff8OwT7QpLm7L2yYr85XNWe9Rbqlbeb9asNXJTHO4k=
|
github.com/neo4j/neo4j-go-driver/v5 v5.28.4/go.mod h1:Vff8OwT7QpLm7L2yYr85XNWe9Rbqlbeb9asNXJTHO4k=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/oklog/ulid/v2 v2.0.2 h1:r4fFzBm+bv0wNKNh5eXTwU7i85y5x+uwkxCUTNVQqLc=
|
github.com/oklog/ulid/v2 v2.0.2 h1:r4fFzBm+bv0wNKNh5eXTwU7i85y5x+uwkxCUTNVQqLc=
|
||||||
github.com/oklog/ulid/v2 v2.0.2/go.mod h1:mtBL0Qe/0HAx6/a4Z30qxVIAL1eQDweXq5lxOEiwQ68=
|
github.com/oklog/ulid/v2 v2.0.2/go.mod h1:mtBL0Qe/0HAx6/a4Z30qxVIAL1eQDweXq5lxOEiwQ68=
|
||||||
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
|
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
|
||||||
@@ -1247,6 +1282,8 @@ github.com/sijms/go-ora/v2 v2.9.0/go.mod h1:QgFInVi3ZWyqAiJwzBQA+nbKYKH77tdp1PYo
|
|||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
|
github.com/snowflakedb/gosnowflake v1.18.1 h1:Nb4AWSnSBWe1UKpKTwCZxjYhYo1JH7GgKhO3wW1kR10=
|
||||||
|
github.com/snowflakedb/gosnowflake v1.18.1/go.mod h1:7D4+cLepOWrerVsH+tevW3zdMJ5/WrEN7ZceAC6xBv0=
|
||||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
|
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
|
||||||
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||||
@@ -1650,10 +1687,12 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
@@ -2075,6 +2114,7 @@ google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aO
|
|||||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ var expectedToolSources = []string{
|
|||||||
"postgres",
|
"postgres",
|
||||||
"serverless-spark",
|
"serverless-spark",
|
||||||
"singlestore",
|
"singlestore",
|
||||||
|
"snowflake",
|
||||||
|
"spanner-admin",
|
||||||
"spanner-postgres",
|
"spanner-postgres",
|
||||||
"spanner",
|
"spanner",
|
||||||
"sqlite",
|
"sqlite",
|
||||||
@@ -127,9 +129,9 @@ func TestGetPrebuiltTool(t *testing.T) {
|
|||||||
sqlite_config, _ := Get("sqlite")
|
sqlite_config, _ := Get("sqlite")
|
||||||
neo4jconfig, _ := Get("neo4j")
|
neo4jconfig, _ := Get("neo4j")
|
||||||
healthcare_config, _ := Get("cloud-healthcare")
|
healthcare_config, _ := Get("cloud-healthcare")
|
||||||
|
snowflake_config, _ := Get("snowflake")
|
||||||
if len(alloydb_admin_config) <= 0 {
|
if len(alloydb_admin_config) <= 0 {
|
||||||
t.Fatalf("unexpected error: could not fetch alloydb prebuilt tools yaml")
|
t.Fatalf("unexpected error: could not fetch alloydb admin prebuilt tools yaml")
|
||||||
}
|
}
|
||||||
if len(alloydb_config) <= 0 {
|
if len(alloydb_config) <= 0 {
|
||||||
t.Fatalf("unexpected error: could not fetch alloydb prebuilt tools yaml")
|
t.Fatalf("unexpected error: could not fetch alloydb prebuilt tools yaml")
|
||||||
@@ -197,6 +199,9 @@ func TestGetPrebuiltTool(t *testing.T) {
|
|||||||
if len(singlestore_config) <= 0 {
|
if len(singlestore_config) <= 0 {
|
||||||
t.Fatalf("unexpected error: could not fetch singlestore prebuilt tools yaml")
|
t.Fatalf("unexpected error: could not fetch singlestore prebuilt tools yaml")
|
||||||
}
|
}
|
||||||
|
if len(snowflake_config) <= 0 {
|
||||||
|
t.Fatalf("unexpected error: could not fetch snowflake prebuilt tools yaml")
|
||||||
|
}
|
||||||
if len(spanner_config) <= 0 {
|
if len(spanner_config) <= 0 {
|
||||||
t.Fatalf("unexpected error: could not fetch spanner prebuilt tools yaml")
|
t.Fatalf("unexpected error: could not fetch spanner prebuilt tools yaml")
|
||||||
}
|
}
|
||||||
@@ -218,6 +223,9 @@ func TestGetPrebuiltTool(t *testing.T) {
|
|||||||
if len(healthcare_config) <= 0 {
|
if len(healthcare_config) <= 0 {
|
||||||
t.Fatalf("unexpected error: could not fetch healthcare prebuilt tools yaml")
|
t.Fatalf("unexpected error: could not fetch healthcare prebuilt tools yaml")
|
||||||
}
|
}
|
||||||
|
if len(snowflake_config) <= 0 {
|
||||||
|
t.Fatalf("unexpected error: could not fetch snowflake prebuilt tools yaml")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFailGetPrebuiltTool(t *testing.T) {
|
func TestFailGetPrebuiltTool(t *testing.T) {
|
||||||
|
|||||||
142
internal/prebuiltconfigs/tools/snowflake.yaml
Normal file
142
internal/prebuiltconfigs/tools/snowflake.yaml
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
# Copyright 2026 Google LLC
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
sources:
|
||||||
|
snowflake-source:
|
||||||
|
kind: snowflake
|
||||||
|
account: ${SNOWFLAKE_ACCOUNT}
|
||||||
|
user: ${SNOWFLAKE_USER}
|
||||||
|
password: ${SNOWFLAKE_PASSWORD}
|
||||||
|
database: ${SNOWFLAKE_DATABASE}
|
||||||
|
schema: ${SNOWFLAKE_SCHEMA}
|
||||||
|
warehouse: ${SNOWFLAKE_WAREHOUSE}
|
||||||
|
role: ${SNOWFLAKE_ROLE}
|
||||||
|
|
||||||
|
tools:
|
||||||
|
execute_sql:
|
||||||
|
kind: snowflake-execute-sql
|
||||||
|
source: snowflake-source
|
||||||
|
description: Use this tool to execute SQL.
|
||||||
|
|
||||||
|
list_tables:
|
||||||
|
kind: snowflake-sql
|
||||||
|
source: snowflake-source
|
||||||
|
description: "Lists detailed schema information (object type, columns, constraints, indexes, owner, comment) as JSON for user-created tables. Filters by a comma-separated list of names. If names are omitted, lists all tables in the specified database and schema."
|
||||||
|
statement: |
|
||||||
|
WITH
|
||||||
|
input_param AS (
|
||||||
|
SELECT ? AS param -- Single bind variable here
|
||||||
|
)
|
||||||
|
,
|
||||||
|
all_tables_mode AS (
|
||||||
|
SELECT COALESCE(TRIM(param), '') = '' AS is_all_tables
|
||||||
|
FROM input_param
|
||||||
|
) --SELECT * FROM all_tables_mode;
|
||||||
|
,
|
||||||
|
filtered_table_names AS (
|
||||||
|
SELECT DISTINCT TRIM(LOWER(value)) AS table_name
|
||||||
|
FROM input_param, all_tables_mode, TABLE(SPLIT_TO_TABLE(param, ','))
|
||||||
|
WHERE NOT is_all_tables
|
||||||
|
) -- SELECT * FROM filtered_table_names;
|
||||||
|
,
|
||||||
|
table_info AS (
|
||||||
|
SELECT
|
||||||
|
t.TABLE_CATALOG,
|
||||||
|
t.TABLE_SCHEMA,
|
||||||
|
t.TABLE_NAME,
|
||||||
|
t.TABLE_TYPE,
|
||||||
|
t.TABLE_OWNER,
|
||||||
|
t.COMMENT
|
||||||
|
FROM
|
||||||
|
all_tables_mode
|
||||||
|
CROSS JOIN ${SNOWFLAKE_DATABASE}.INFORMATION_SCHEMA.TABLES T
|
||||||
|
WHERE
|
||||||
|
t.TABLE_TYPE = 'BASE TABLE'
|
||||||
|
AND t.TABLE_SCHEMA NOT IN ('INFORMATION_SCHEMA')
|
||||||
|
AND t.TABLE_SCHEMA = '${SNOWFLAKE_SCHEMA}'
|
||||||
|
AND is_all_tables OR LOWER(T.TABLE_NAME) IN (SELECT table_name FROM filtered_table_names)
|
||||||
|
) -- SELECT * FROM table_info;
|
||||||
|
,
|
||||||
|
columns_info AS (
|
||||||
|
SELECT
|
||||||
|
c.TABLE_CATALOG AS database_name,
|
||||||
|
c.TABLE_SCHEMA AS schema_name,
|
||||||
|
c.TABLE_NAME AS table_name,
|
||||||
|
c.COLUMN_NAME AS column_name,
|
||||||
|
c.DATA_TYPE AS data_type,
|
||||||
|
c.ORDINAL_POSITION AS column_ordinal_position,
|
||||||
|
c.IS_NULLABLE AS is_nullable,
|
||||||
|
c.COLUMN_DEFAULT AS column_default,
|
||||||
|
c.COMMENT AS column_comment
|
||||||
|
FROM
|
||||||
|
${SNOWFLAKE_DATABASE}.INFORMATION_SCHEMA.COLUMNS c
|
||||||
|
INNER JOIN table_info USING (TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME)
|
||||||
|
)
|
||||||
|
,
|
||||||
|
constraints_info AS (
|
||||||
|
SELECT
|
||||||
|
tc.TABLE_CATALOG AS database_name,
|
||||||
|
tc.TABLE_SCHEMA AS schema_name,
|
||||||
|
tc.TABLE_NAME AS table_name,
|
||||||
|
tc.CONSTRAINT_NAME AS constraint_name,
|
||||||
|
tc.CONSTRAINT_TYPE AS constraint_type
|
||||||
|
FROM
|
||||||
|
${SNOWFLAKE_DATABASE}.INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
|
||||||
|
INNER JOIN table_info USING (TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME)
|
||||||
|
GROUP BY
|
||||||
|
tc.TABLE_CATALOG, tc.TABLE_SCHEMA, tc.TABLE_NAME, tc.CONSTRAINT_NAME, tc.CONSTRAINT_TYPE
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
ti.TABLE_SCHEMA AS schema_name,
|
||||||
|
ti.TABLE_NAME AS object_name,
|
||||||
|
OBJECT_CONSTRUCT(
|
||||||
|
'schema_name', ti.TABLE_SCHEMA,
|
||||||
|
'object_name', ti.TABLE_NAME,
|
||||||
|
'object_type', ti.TABLE_TYPE,
|
||||||
|
'owner', ti.TABLE_OWNER,
|
||||||
|
'comment', ti.COMMENT,
|
||||||
|
'columns', COALESCE(
|
||||||
|
(SELECT ARRAY_AGG(
|
||||||
|
OBJECT_CONSTRUCT(
|
||||||
|
'column_name', ci.column_name,
|
||||||
|
'data_type', ci.data_type,
|
||||||
|
'ordinal_position', ci.column_ordinal_position,
|
||||||
|
'is_nullable', ci.is_nullable,
|
||||||
|
'column_default', ci.column_default,
|
||||||
|
'column_comment', ci.column_comment
|
||||||
|
)
|
||||||
|
) FROM columns_info ci WHERE ci.table_name = ti.TABLE_NAME AND ci.schema_name = ti.TABLE_SCHEMA),
|
||||||
|
ARRAY_CONSTRUCT()
|
||||||
|
),
|
||||||
|
'constraints', COALESCE(
|
||||||
|
(SELECT ARRAY_AGG(
|
||||||
|
OBJECT_CONSTRUCT(
|
||||||
|
'constraint_name', cons.constraint_name,
|
||||||
|
'constraint_type', cons.constraint_type
|
||||||
|
)
|
||||||
|
) FROM constraints_info cons WHERE cons.table_name = ti.TABLE_NAME AND cons.schema_name = ti.TABLE_SCHEMA),
|
||||||
|
ARRAY_CONSTRUCT()
|
||||||
|
)
|
||||||
|
) AS object_details
|
||||||
|
FROM table_info ti
|
||||||
|
ORDER BY ti.TABLE_SCHEMA, ti.TABLE_NAME;
|
||||||
|
parameters:
|
||||||
|
- name: table_names
|
||||||
|
type: string
|
||||||
|
description: "Optional: A comma-separated list of table names. If empty, details for all tables in the specified database and schema will be listed."
|
||||||
|
|
||||||
|
toolsets:
|
||||||
|
snowflake_tools:
|
||||||
|
- execute_sql
|
||||||
|
- list_tables
|
||||||
27
internal/prebuiltconfigs/tools/spanner-admin.yaml
Normal file
27
internal/prebuiltconfigs/tools/spanner-admin.yaml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Copyright 2026 Google LLC
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
sources:
|
||||||
|
spanner-admin-source:
|
||||||
|
kind: spanner-admin
|
||||||
|
defaultProject: ${SPANNER_PROJECT:}
|
||||||
|
|
||||||
|
tools:
|
||||||
|
create_instance:
|
||||||
|
kind: spanner-create-instance
|
||||||
|
source: spanner-admin-source
|
||||||
|
|
||||||
|
toolsets:
|
||||||
|
spanner_admin_tools:
|
||||||
|
- create_instance
|
||||||
@@ -68,6 +68,8 @@ type ServerConfig struct {
|
|||||||
UI bool
|
UI bool
|
||||||
// Specifies a list of origins permitted to access this server.
|
// Specifies a list of origins permitted to access this server.
|
||||||
AllowedOrigins []string
|
AllowedOrigins []string
|
||||||
|
// Specifies a list of hosts permitted to access this server
|
||||||
|
AllowedHosts []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type logFormat string
|
type logFormat string
|
||||||
|
|||||||
@@ -300,6 +300,21 @@ func InitializeConfigs(ctx context.Context, cfg ServerConfig) (
|
|||||||
return sourcesMap, authServicesMap, embeddingModelsMap, toolsMap, toolsetsMap, promptsMap, promptsetsMap, nil
|
return sourcesMap, authServicesMap, embeddingModelsMap, toolsMap, toolsetsMap, promptsMap, promptsetsMap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hostCheck(allowedHosts map[string]struct{}) func(http.Handler) http.Handler {
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
_, hasWildcard := allowedHosts["*"]
|
||||||
|
_, hostIsAllowed := allowedHosts[r.Host]
|
||||||
|
if !hasWildcard && !hostIsAllowed {
|
||||||
|
// Return 400 Bad Request or 403 Forbidden to block the attack
|
||||||
|
http.Error(w, "Invalid Host header", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NewServer returns a Server object based on provided Config.
|
// NewServer returns a Server object based on provided Config.
|
||||||
func NewServer(ctx context.Context, cfg ServerConfig) (*Server, error) {
|
func NewServer(ctx context.Context, cfg ServerConfig) (*Server, error) {
|
||||||
instrumentation, err := util.InstrumentationFromContext(ctx)
|
instrumentation, err := util.InstrumentationFromContext(ctx)
|
||||||
@@ -374,7 +389,7 @@ func NewServer(ctx context.Context, cfg ServerConfig) (*Server, error) {
|
|||||||
|
|
||||||
// cors
|
// cors
|
||||||
if slices.Contains(cfg.AllowedOrigins, "*") {
|
if slices.Contains(cfg.AllowedOrigins, "*") {
|
||||||
s.logger.WarnContext(ctx, "wildcard (`*`) allows all origin to access the resource and is not secure. Use it with cautious for public, non-sensitive data, or during local development. Recommended to use `--allowed-origins` flag to prevent DNS rebinding attacks")
|
s.logger.WarnContext(ctx, "wildcard (`*`) allows all origin to access the resource and is not secure. Use it with cautious for public, non-sensitive data, or during local development. Recommended to use `--allowed-origins` flag")
|
||||||
}
|
}
|
||||||
corsOpts := cors.Options{
|
corsOpts := cors.Options{
|
||||||
AllowedOrigins: cfg.AllowedOrigins,
|
AllowedOrigins: cfg.AllowedOrigins,
|
||||||
@@ -385,6 +400,15 @@ func NewServer(ctx context.Context, cfg ServerConfig) (*Server, error) {
|
|||||||
MaxAge: 300, // cache preflight results for 5 minutes
|
MaxAge: 300, // cache preflight results for 5 minutes
|
||||||
}
|
}
|
||||||
r.Use(cors.Handler(corsOpts))
|
r.Use(cors.Handler(corsOpts))
|
||||||
|
// validate hosts for DNS rebinding attacks
|
||||||
|
if slices.Contains(cfg.AllowedHosts, "*") {
|
||||||
|
s.logger.WarnContext(ctx, "wildcard (`*`) allows all hosts to access the resource and is not secure. Use it with cautious for public, non-sensitive data, or during local development. Recommended to use `--allowed-hosts` flag to prevent DNS rebinding attacks")
|
||||||
|
}
|
||||||
|
allowedHostsMap := make(map[string]struct{}, len(cfg.AllowedHosts))
|
||||||
|
for _, h := range cfg.AllowedHosts {
|
||||||
|
allowedHostsMap[h] = struct{}{}
|
||||||
|
}
|
||||||
|
r.Use(hostCheck(allowedHostsMap))
|
||||||
|
|
||||||
// control plane
|
// control plane
|
||||||
apiR, err := apiRouter(s)
|
apiR, err := apiRouter(s)
|
||||||
|
|||||||
@@ -43,9 +43,10 @@ func TestServe(t *testing.T) {
|
|||||||
|
|
||||||
addr, port := "127.0.0.1", 5000
|
addr, port := "127.0.0.1", 5000
|
||||||
cfg := server.ServerConfig{
|
cfg := server.ServerConfig{
|
||||||
Version: "0.0.0",
|
Version: "0.0.0",
|
||||||
Address: addr,
|
Address: addr,
|
||||||
Port: port,
|
Port: port,
|
||||||
|
AllowedHosts: []string{"*"},
|
||||||
}
|
}
|
||||||
|
|
||||||
otelShutdown, err := telemetry.SetupOTel(ctx, "0.0.0", "", false, "toolbox")
|
otelShutdown, err := telemetry.SetupOTel(ctx, "0.0.0", "", false, "toolbox")
|
||||||
|
|||||||
159
internal/sources/snowflake/snowflake.go
Normal file
159
internal/sources/snowflake/snowflake.go
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
// Copyright 2026 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package snowflake
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/goccy/go-yaml"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
_ "github.com/snowflakedb/gosnowflake"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
const SourceKind string = "snowflake"
|
||||||
|
|
||||||
|
// validate interface
|
||||||
|
var _ sources.SourceConfig = Config{}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if !sources.Register(SourceKind, newConfig) {
|
||||||
|
panic(fmt.Sprintf("source kind %q already registered", SourceKind))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) {
|
||||||
|
actual := Config{Name: name}
|
||||||
|
if err := decoder.DecodeContext(ctx, &actual); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return actual, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Name string `yaml:"name" validate:"required"`
|
||||||
|
Kind string `yaml:"kind" validate:"required"`
|
||||||
|
Account string `yaml:"account" validate:"required"`
|
||||||
|
User string `yaml:"user" validate:"required"`
|
||||||
|
Password string `yaml:"password" validate:"required"`
|
||||||
|
Database string `yaml:"database" validate:"required"`
|
||||||
|
Schema string `yaml:"schema" validate:"required"`
|
||||||
|
Warehouse string `yaml:"warehouse"`
|
||||||
|
Role string `yaml:"role"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Config) SourceConfigKind() string {
|
||||||
|
return SourceKind
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) {
|
||||||
|
db, err := initSnowflakeConnection(ctx, tracer, r.Name, r.Account, r.User, r.Password, r.Database, r.Schema, r.Warehouse, r.Role)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to create connection: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.PingContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to connect successfully: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &Source{
|
||||||
|
Config: r,
|
||||||
|
DB: db,
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ sources.Source = &Source{}
|
||||||
|
|
||||||
|
type Source struct {
|
||||||
|
Config
|
||||||
|
DB *sqlx.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Source) SourceKind() string {
|
||||||
|
return SourceKind
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Source) ToConfig() sources.SourceConfig {
|
||||||
|
return s.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Source) SnowflakeDB() *sqlx.DB {
|
||||||
|
return s.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Source) RunSQL(ctx context.Context, statement string, params []any) (any, error) {
|
||||||
|
rows, err := s.DB.QueryxContext(ctx, statement, params...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to execute query: %w", err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var out []any
|
||||||
|
for rows.Next() {
|
||||||
|
cols, err := rows.Columns()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to get columns: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
values := make([]interface{}, len(cols))
|
||||||
|
valuePtrs := make([]interface{}, len(cols))
|
||||||
|
for i := range values {
|
||||||
|
valuePtrs[i] = &values[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Scan(valuePtrs...); err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to scan row: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
vMap := make(map[string]any)
|
||||||
|
for i, col := range cols {
|
||||||
|
vMap[col] = values[i]
|
||||||
|
}
|
||||||
|
out = append(out, vMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, fmt.Errorf("row iteration error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func initSnowflakeConnection(ctx context.Context, tracer trace.Tracer, name, account, user, password, database, schema, warehouse, role string) (*sqlx.DB, error) {
|
||||||
|
//nolint:all // Reassigned ctx
|
||||||
|
ctx, span := sources.InitConnectionSpan(ctx, tracer, SourceKind, name)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
// Set defaults for optional parameters
|
||||||
|
if warehouse == "" {
|
||||||
|
warehouse = "COMPUTE_WH"
|
||||||
|
}
|
||||||
|
if role == "" {
|
||||||
|
role = "ACCOUNTADMIN"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Snowflake DSN format: user:password@account/database/schema?warehouse=warehouse&role=role
|
||||||
|
dsn := fmt.Sprintf("%s:%s@%s/%s/%s?warehouse=%s&role=%s", user, password, account, database, schema, warehouse, role)
|
||||||
|
db, err := sqlx.ConnectContext(ctx, "snowflake", dsn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to create connection: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return db, nil
|
||||||
|
}
|
||||||
129
internal/sources/snowflake/snowflake_test.go
Normal file
129
internal/sources/snowflake/snowflake_test.go
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
// Copyright 2026 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package snowflake_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
yaml "github.com/goccy/go-yaml"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/server"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/sources/snowflake"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseFromYamlSnowflake(t *testing.T) {
|
||||||
|
tcs := []struct {
|
||||||
|
desc string
|
||||||
|
in string
|
||||||
|
want server.SourceConfigs
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "basic example",
|
||||||
|
in: `
|
||||||
|
sources:
|
||||||
|
my-snowflake-instance:
|
||||||
|
kind: snowflake
|
||||||
|
account: my-account
|
||||||
|
user: my_user
|
||||||
|
password: my_pass
|
||||||
|
database: my_db
|
||||||
|
schema: my_schema
|
||||||
|
`,
|
||||||
|
want: server.SourceConfigs{
|
||||||
|
"my-snowflake-instance": snowflake.Config{
|
||||||
|
Name: "my-snowflake-instance",
|
||||||
|
Kind: snowflake.SourceKind,
|
||||||
|
Account: "my-account",
|
||||||
|
User: "my_user",
|
||||||
|
Password: "my_pass",
|
||||||
|
Database: "my_db",
|
||||||
|
Schema: "my_schema",
|
||||||
|
Warehouse: "",
|
||||||
|
Role: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tcs {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
got := struct {
|
||||||
|
Sources server.SourceConfigs `yaml:"sources"`
|
||||||
|
}{}
|
||||||
|
// Parse contents
|
||||||
|
err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to unmarshal: %s", err)
|
||||||
|
}
|
||||||
|
if !cmp.Equal(tc.want, got.Sources) {
|
||||||
|
t.Fatalf("incorrect parse: want %v, got %v", tc.want, got.Sources)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFailParseFromYaml(t *testing.T) {
|
||||||
|
tcs := []struct {
|
||||||
|
desc string
|
||||||
|
in string
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "extra field",
|
||||||
|
in: `
|
||||||
|
sources:
|
||||||
|
my-snowflake-instance:
|
||||||
|
kind: snowflake
|
||||||
|
account: my-account
|
||||||
|
user: my_user
|
||||||
|
password: my_pass
|
||||||
|
database: my_db
|
||||||
|
schema: my_schema
|
||||||
|
foo: bar
|
||||||
|
`,
|
||||||
|
err: "unable to parse source \"my-snowflake-instance\" as \"snowflake\": [3:1] unknown field \"foo\"\n 1 | account: my-account\n 2 | database: my_db\n> 3 | foo: bar\n ^\n 4 | kind: snowflake\n 5 | password: my_pass\n 6 | schema: my_schema\n 7 | ",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "missing required field",
|
||||||
|
in: `
|
||||||
|
sources:
|
||||||
|
my-snowflake-instance:
|
||||||
|
kind: snowflake
|
||||||
|
account: my-account
|
||||||
|
user: my_user
|
||||||
|
password: my_pass
|
||||||
|
database: my_db
|
||||||
|
`,
|
||||||
|
err: "unable to parse source \"my-snowflake-instance\" as \"snowflake\": Key: 'Config.Schema' Error:Field validation for 'Schema' failed on the 'required' tag",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tcs {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
got := struct {
|
||||||
|
Sources server.SourceConfigs `yaml:"sources"`
|
||||||
|
}{}
|
||||||
|
// Parse contents
|
||||||
|
err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expect parsing to fail")
|
||||||
|
}
|
||||||
|
errStr := err.Error()
|
||||||
|
if errStr != tc.err {
|
||||||
|
t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
120
internal/sources/spanneradmin/spanneradmin.go
Normal file
120
internal/sources/spanneradmin/spanneradmin.go
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
// Copyright 2026 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package spanneradmin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
instance "cloud.google.com/go/spanner/admin/instance/apiv1"
|
||||||
|
"github.com/goccy/go-yaml"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/util"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"google.golang.org/api/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
const SourceKind string = "spanner-admin"
|
||||||
|
|
||||||
|
// validate interface
|
||||||
|
var _ sources.SourceConfig = Config{}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if !sources.Register(SourceKind, newConfig) {
|
||||||
|
panic(fmt.Sprintf("source kind %q already registered", SourceKind))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) {
|
||||||
|
actual := Config{Name: name}
|
||||||
|
if err := decoder.DecodeContext(ctx, &actual); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return actual, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Name string `yaml:"name" validate:"required"`
|
||||||
|
Kind string `yaml:"kind" validate:"required"`
|
||||||
|
DefaultProject string `yaml:"defaultProject"`
|
||||||
|
UseClientOAuth bool `yaml:"useClientOAuth"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Config) SourceConfigKind() string {
|
||||||
|
return SourceKind
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize initializes a Spanner Admin Source instance.
|
||||||
|
func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) {
|
||||||
|
var client *instance.InstanceAdminClient
|
||||||
|
|
||||||
|
if !r.UseClientOAuth {
|
||||||
|
ua, err := util.UserAgentFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error in User Agent retrieval: %s", err)
|
||||||
|
}
|
||||||
|
// Use Application Default Credentials
|
||||||
|
client, err = instance.NewInstanceAdminClient(ctx, option.WithUserAgent(ua))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error creating new spanner instance admin client: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &Source{
|
||||||
|
Config: r,
|
||||||
|
Client: client,
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ sources.Source = &Source{}
|
||||||
|
|
||||||
|
type Source struct {
|
||||||
|
Config
|
||||||
|
Client *instance.InstanceAdminClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Source) SourceKind() string {
|
||||||
|
return SourceKind
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Source) ToConfig() sources.SourceConfig {
|
||||||
|
return s.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Source) GetDefaultProject() string {
|
||||||
|
return s.DefaultProject
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Source) GetClient(ctx context.Context, accessToken string) (*instance.InstanceAdminClient, error) {
|
||||||
|
if s.UseClientOAuth {
|
||||||
|
token := &oauth2.Token{AccessToken: accessToken}
|
||||||
|
ua, err := util.UserAgentFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
client, err := instance.NewInstanceAdminClient(ctx, option.WithTokenSource(oauth2.StaticTokenSource(token)), option.WithUserAgent(ua))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error creating new spanner instance admin client: %w", err)
|
||||||
|
}
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
return s.Client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Source) UseClientAuthorization() bool {
|
||||||
|
return s.UseClientOAuth
|
||||||
|
}
|
||||||
135
internal/sources/spanneradmin/spanneradmin_test.go
Normal file
135
internal/sources/spanneradmin/spanneradmin_test.go
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
// Copyright 2026 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package spanneradmin_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
yaml "github.com/goccy/go-yaml"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/server"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/sources/spanneradmin"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseFromYamlSpannerAdmin(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
tcs := []struct {
|
||||||
|
desc string
|
||||||
|
in string
|
||||||
|
want server.SourceConfigs
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "basic example",
|
||||||
|
in: `
|
||||||
|
sources:
|
||||||
|
my-spanner-admin-instance:
|
||||||
|
kind: spanner-admin
|
||||||
|
`,
|
||||||
|
want: map[string]sources.SourceConfig{
|
||||||
|
"my-spanner-admin-instance": spanneradmin.Config{
|
||||||
|
Name: "my-spanner-admin-instance",
|
||||||
|
Kind: spanneradmin.SourceKind,
|
||||||
|
UseClientOAuth: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "use client auth example",
|
||||||
|
in: `
|
||||||
|
sources:
|
||||||
|
my-spanner-admin-instance:
|
||||||
|
kind: spanner-admin
|
||||||
|
useClientOAuth: true
|
||||||
|
`,
|
||||||
|
want: map[string]sources.SourceConfig{
|
||||||
|
"my-spanner-admin-instance": spanneradmin.Config{
|
||||||
|
Name: "my-spanner-admin-instance",
|
||||||
|
Kind: spanneradmin.SourceKind,
|
||||||
|
UseClientOAuth: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tcs {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
got := struct {
|
||||||
|
Sources server.SourceConfigs `yaml:"sources"`
|
||||||
|
}{}
|
||||||
|
// Parse contents
|
||||||
|
err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to unmarshal: %s", err)
|
||||||
|
}
|
||||||
|
if !cmp.Equal(tc.want, got.Sources) {
|
||||||
|
t.Fatalf("incorrect parse: want %v, got %v", tc.want, got.Sources)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFailParseFromYaml(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
tcs := []struct {
|
||||||
|
desc string
|
||||||
|
in string
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "extra field",
|
||||||
|
in: `
|
||||||
|
sources:
|
||||||
|
my-spanner-admin-instance:
|
||||||
|
kind: spanner-admin
|
||||||
|
project: test-project
|
||||||
|
`,
|
||||||
|
err: `unable to parse source "my-spanner-admin-instance" as "spanner-admin": [2:1] unknown field "project"
|
||||||
|
1 | kind: spanner-admin
|
||||||
|
> 2 | project: test-project
|
||||||
|
^
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "missing required field",
|
||||||
|
in: `
|
||||||
|
sources:
|
||||||
|
my-spanner-admin-instance:
|
||||||
|
useClientOAuth: true
|
||||||
|
`,
|
||||||
|
err: "missing 'kind' field for source \"my-spanner-admin-instance\"",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tcs {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
got := struct {
|
||||||
|
Sources server.SourceConfigs `yaml:"sources"`
|
||||||
|
}{}
|
||||||
|
// Parse contents
|
||||||
|
err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expect parsing to fail")
|
||||||
|
}
|
||||||
|
errStr := err.Error()
|
||||||
|
if errStr != tc.err {
|
||||||
|
t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,14 +16,17 @@ package trino
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/goccy/go-yaml"
|
"github.com/goccy/go-yaml"
|
||||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||||
_ "github.com/trinodb/trino-go-client/trino"
|
"github.com/googleapis/genai-toolbox/internal/util"
|
||||||
|
trinogo "github.com/trinodb/trino-go-client/trino"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -47,18 +50,21 @@ func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Name string `yaml:"name" validate:"required"`
|
Name string `yaml:"name" validate:"required"`
|
||||||
Kind string `yaml:"kind" validate:"required"`
|
Kind string `yaml:"kind" validate:"required"`
|
||||||
Host string `yaml:"host" validate:"required"`
|
Host string `yaml:"host" validate:"required"`
|
||||||
Port string `yaml:"port" validate:"required"`
|
Port string `yaml:"port" validate:"required"`
|
||||||
User string `yaml:"user"`
|
User string `yaml:"user"`
|
||||||
Password string `yaml:"password"`
|
Password string `yaml:"password"`
|
||||||
Catalog string `yaml:"catalog" validate:"required"`
|
Catalog string `yaml:"catalog" validate:"required"`
|
||||||
Schema string `yaml:"schema" validate:"required"`
|
Schema string `yaml:"schema" validate:"required"`
|
||||||
QueryTimeout string `yaml:"queryTimeout"`
|
QueryTimeout string `yaml:"queryTimeout"`
|
||||||
AccessToken string `yaml:"accessToken"`
|
AccessToken string `yaml:"accessToken"`
|
||||||
KerberosEnabled bool `yaml:"kerberosEnabled"`
|
KerberosEnabled bool `yaml:"kerberosEnabled"`
|
||||||
SSLEnabled bool `yaml:"sslEnabled"`
|
SSLEnabled bool `yaml:"sslEnabled"`
|
||||||
|
SSLCertPath string `yaml:"sslCertPath"`
|
||||||
|
SSLCert string `yaml:"sslCert"`
|
||||||
|
DisableSslVerification bool `yaml:"disableSslVerification"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r Config) SourceConfigKind() string {
|
func (r Config) SourceConfigKind() string {
|
||||||
@@ -66,7 +72,7 @@ func (r Config) SourceConfigKind() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) {
|
func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) {
|
||||||
pool, err := initTrinoConnectionPool(ctx, tracer, r.Name, r.Host, r.Port, r.User, r.Password, r.Catalog, r.Schema, r.QueryTimeout, r.AccessToken, r.KerberosEnabled, r.SSLEnabled)
|
pool, err := initTrinoConnectionPool(ctx, tracer, r.Name, r.Host, r.Port, r.User, r.Password, r.Catalog, r.Schema, r.QueryTimeout, r.AccessToken, r.KerberosEnabled, r.SSLEnabled, r.SSLCertPath, r.SSLCert, r.DisableSslVerification)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to create pool: %w", err)
|
return nil, fmt.Errorf("unable to create pool: %w", err)
|
||||||
}
|
}
|
||||||
@@ -152,17 +158,35 @@ func (s *Source) RunSQL(ctx context.Context, statement string, params []any) (an
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func initTrinoConnectionPool(ctx context.Context, tracer trace.Tracer, name, host, port, user, password, catalog, schema, queryTimeout, accessToken string, kerberosEnabled, sslEnabled bool) (*sql.DB, error) {
|
func initTrinoConnectionPool(ctx context.Context, tracer trace.Tracer, name, host, port, user, password, catalog, schema, queryTimeout, accessToken string, kerberosEnabled, sslEnabled bool, sslCertPath, sslCert string, disableSslVerification bool) (*sql.DB, error) {
|
||||||
//nolint:all // Reassigned ctx
|
//nolint:all // Reassigned ctx
|
||||||
ctx, span := sources.InitConnectionSpan(ctx, tracer, SourceKind, name)
|
ctx, span := sources.InitConnectionSpan(ctx, tracer, SourceKind, name)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
// Build Trino DSN
|
// Build Trino DSN
|
||||||
dsn, err := buildTrinoDSN(host, port, user, password, catalog, schema, queryTimeout, accessToken, kerberosEnabled, sslEnabled)
|
dsn, err := buildTrinoDSN(host, port, user, password, catalog, schema, queryTimeout, accessToken, kerberosEnabled, sslEnabled, sslCertPath, sslCert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to build DSN: %w", err)
|
return nil, fmt.Errorf("failed to build DSN: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger, err := util.LoggerFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to get logger from ctx: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if disableSslVerification {
|
||||||
|
logger.WarnContext(ctx, "SSL verification is disabled for trino source %s. This is an insecure setting and should not be used in production.\n", name)
|
||||||
|
tr := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
}
|
||||||
|
client := &http.Client{Transport: tr}
|
||||||
|
clientName := fmt.Sprintf("insecure_trino_client_%s", name)
|
||||||
|
if err := trinogo.RegisterCustomClient(clientName, client); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to register custom client: %w", err)
|
||||||
|
}
|
||||||
|
dsn = fmt.Sprintf("%s&custom_client=%s", dsn, clientName)
|
||||||
|
}
|
||||||
|
|
||||||
db, err := sql.Open("trino", dsn)
|
db, err := sql.Open("trino", dsn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to open connection: %w", err)
|
return nil, fmt.Errorf("failed to open connection: %w", err)
|
||||||
@@ -176,7 +200,7 @@ func initTrinoConnectionPool(ctx context.Context, tracer trace.Tracer, name, hos
|
|||||||
return db, nil
|
return db, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildTrinoDSN(host, port, user, password, catalog, schema, queryTimeout, accessToken string, kerberosEnabled, sslEnabled bool) (string, error) {
|
func buildTrinoDSN(host, port, user, password, catalog, schema, queryTimeout, accessToken string, kerberosEnabled, sslEnabled bool, sslCertPath, sslCert string) (string, error) {
|
||||||
// Build query parameters
|
// Build query parameters
|
||||||
query := url.Values{}
|
query := url.Values{}
|
||||||
query.Set("catalog", catalog)
|
query.Set("catalog", catalog)
|
||||||
@@ -190,6 +214,12 @@ func buildTrinoDSN(host, port, user, password, catalog, schema, queryTimeout, ac
|
|||||||
if kerberosEnabled {
|
if kerberosEnabled {
|
||||||
query.Set("KerberosEnabled", "true")
|
query.Set("KerberosEnabled", "true")
|
||||||
}
|
}
|
||||||
|
if sslCertPath != "" {
|
||||||
|
query.Set("sslCertPath", sslCertPath)
|
||||||
|
}
|
||||||
|
if sslCert != "" {
|
||||||
|
query.Set("sslCert", sslCert)
|
||||||
|
}
|
||||||
|
|
||||||
// Build URL
|
// Build URL
|
||||||
scheme := "http"
|
scheme := "http"
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ func TestBuildTrinoDSN(t *testing.T) {
|
|||||||
accessToken string
|
accessToken string
|
||||||
kerberosEnabled bool
|
kerberosEnabled bool
|
||||||
sslEnabled bool
|
sslEnabled bool
|
||||||
|
sslCertPath string
|
||||||
|
sslCert string
|
||||||
want string
|
want string
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
@@ -49,6 +51,19 @@ func TestBuildTrinoDSN(t *testing.T) {
|
|||||||
want: "http://testuser@localhost:8080?catalog=hive&schema=default",
|
want: "http://testuser@localhost:8080?catalog=hive&schema=default",
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "with SSL cert path and cert",
|
||||||
|
host: "localhost",
|
||||||
|
port: "8443",
|
||||||
|
user: "testuser",
|
||||||
|
catalog: "hive",
|
||||||
|
schema: "default",
|
||||||
|
sslEnabled: true,
|
||||||
|
sslCertPath: "/path/to/cert.pem",
|
||||||
|
sslCert: "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----\n",
|
||||||
|
want: "https://testuser@localhost:8443?catalog=hive&schema=default&sslCert=-----BEGIN+CERTIFICATE-----%0A...%0A-----END+CERTIFICATE-----%0A&sslCertPath=%2Fpath%2Fto%2Fcert.pem",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "with password",
|
name: "with password",
|
||||||
host: "localhost",
|
host: "localhost",
|
||||||
@@ -117,7 +132,7 @@ func TestBuildTrinoDSN(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := buildTrinoDSN(tt.host, tt.port, tt.user, tt.password, tt.catalog, tt.schema, tt.queryTimeout, tt.accessToken, tt.kerberosEnabled, tt.sslEnabled)
|
got, err := buildTrinoDSN(tt.host, tt.port, tt.user, tt.password, tt.catalog, tt.schema, tt.queryTimeout, tt.accessToken, tt.kerberosEnabled, tt.sslEnabled, tt.sslCertPath, tt.sslCert)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("buildTrinoDSN() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("buildTrinoDSN() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
@@ -215,6 +230,41 @@ func TestParseFromYamlTrino(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "example with SSL cert path and cert",
|
||||||
|
in: `
|
||||||
|
sources:
|
||||||
|
my-trino-ssl-cert:
|
||||||
|
kind: trino
|
||||||
|
host: localhost
|
||||||
|
port: "8443"
|
||||||
|
user: testuser
|
||||||
|
catalog: hive
|
||||||
|
schema: default
|
||||||
|
sslEnabled: true
|
||||||
|
sslCertPath: /path/to/cert.pem
|
||||||
|
sslCert: |-
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
...
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
disableSslVerification: true
|
||||||
|
`,
|
||||||
|
want: server.SourceConfigs{
|
||||||
|
"my-trino-ssl-cert": Config{
|
||||||
|
Name: "my-trino-ssl-cert",
|
||||||
|
Kind: SourceKind,
|
||||||
|
Host: "localhost",
|
||||||
|
Port: "8443",
|
||||||
|
User: "testuser",
|
||||||
|
Catalog: "hive",
|
||||||
|
Schema: "default",
|
||||||
|
SSLEnabled: true,
|
||||||
|
SSLCertPath: "/path/to/cert.pem",
|
||||||
|
SSLCert: "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
|
||||||
|
DisableSslVerification: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range tcs {
|
for _, tc := range tcs {
|
||||||
t.Run(tc.desc, func(t *testing.T) {
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
|||||||
@@ -77,26 +77,21 @@ func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error)
|
|||||||
placeholderParts = append(placeholderParts, fmt.Sprintf("$%d", i+3)) // $1, $2 reserved
|
placeholderParts = append(placeholderParts, fmt.Sprintf("$%d", i+3)) // $1, $2 reserved
|
||||||
}
|
}
|
||||||
|
|
||||||
var paramNamesSQL string
|
var stmt string
|
||||||
var paramValuesSQL string
|
|
||||||
|
|
||||||
if numParams > 0 {
|
if numParams > 0 {
|
||||||
paramNamesSQL = fmt.Sprintf("ARRAY[%s]", strings.Join(quotedNameParts, ", "))
|
paramNamesSQL := fmt.Sprintf("ARRAY[%s]", strings.Join(quotedNameParts, ", "))
|
||||||
paramValuesSQL = fmt.Sprintf("ARRAY[%s]", strings.Join(placeholderParts, ", "))
|
paramValuesSQL := fmt.Sprintf("ARRAY[%s]", strings.Join(placeholderParts, ", "))
|
||||||
|
// execute_nl_query is the AlloyDB AI function that executes the natural language query
|
||||||
|
// The first parameter is the natural language query, which is passed as $1
|
||||||
|
// The second parameter is the NLConfig, which is passed as a $2
|
||||||
|
// The following params are the list of PSV values passed to the NLConfig
|
||||||
|
// Example SQL statement being executed:
|
||||||
|
// SELECT alloydb_ai_nl.execute_nl_query(nl_question => 'How many tickets do I have?', nl_config_id => 'cymbal_air_nl_config', param_names => ARRAY ['user_email'], param_values => ARRAY ['hailongli@google.com']);
|
||||||
|
stmtFormat := "SELECT alloydb_ai_nl.execute_nl_query(nl_question => $1, nl_config_id => $2, param_names => %s, param_values => %s);"
|
||||||
|
stmt = fmt.Sprintf(stmtFormat, paramNamesSQL, paramValuesSQL)
|
||||||
} else {
|
} else {
|
||||||
paramNamesSQL = "ARRAY[]::TEXT[]"
|
stmt = "SELECT alloydb_ai_nl.execute_nl_query(nl_question => $1, nl_config_id => $2);"
|
||||||
paramValuesSQL = "ARRAY[]::TEXT[]"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// execute_nl_query is the AlloyDB AI function that executes the natural language query
|
|
||||||
// The first parameter is the natural language query, which is passed as $1
|
|
||||||
// The second parameter is the NLConfig, which is passed as a $2
|
|
||||||
// The following params are the list of PSV values passed to the NLConfig
|
|
||||||
// Example SQL statement being executed:
|
|
||||||
// SELECT alloydb_ai_nl.execute_nl_query(nl_question => 'How many tickets do I have?', nl_config_id => 'cymbal_air_nl_config', param_names => ARRAY ['user_email'], param_values => ARRAY ['hailongli@google.com']);
|
|
||||||
stmtFormat := "SELECT alloydb_ai_nl.execute_nl_query(nl_question => $1, nl_config_id => $2, param_names => %s, param_values => %s);"
|
|
||||||
stmt := fmt.Sprintf(stmtFormat, paramNamesSQL, paramValuesSQL)
|
|
||||||
|
|
||||||
newQuestionParam := parameters.NewStringParameter(
|
newQuestionParam := parameters.NewStringParameter(
|
||||||
"question", // name
|
"question", // name
|
||||||
"The natural language question to ask.", // description
|
"The natural language question to ask.", // description
|
||||||
|
|||||||
@@ -0,0 +1,143 @@
|
|||||||
|
// Copyright 2026 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package snowflakeexecutesql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
yaml "github.com/goccy/go-yaml"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/embeddingmodels"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/util"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/util/parameters"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
const kind string = "snowflake-execute-sql"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if !tools.Register(kind, newConfig) {
|
||||||
|
panic(fmt.Sprintf("tool kind %q already registered", kind))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
|
||||||
|
actual := Config{Name: name}
|
||||||
|
if err := decoder.DecodeContext(ctx, &actual); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return actual, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type compatibleSource interface {
|
||||||
|
SnowflakeDB() *sqlx.DB
|
||||||
|
RunSQL(context.Context, string, []any) (any, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Name string `yaml:"name" validate:"required"`
|
||||||
|
Kind string `yaml:"kind" validate:"required"`
|
||||||
|
Source string `yaml:"source" validate:"required"`
|
||||||
|
Description string `yaml:"description" validate:"required"`
|
||||||
|
AuthRequired []string `yaml:"authRequired"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate interface
|
||||||
|
var _ tools.ToolConfig = Config{}
|
||||||
|
|
||||||
|
func (cfg Config) ToolConfigKind() string {
|
||||||
|
return kind
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
|
||||||
|
sqlParameter := parameters.NewStringParameter("sql", "The sql to execute.")
|
||||||
|
params := parameters.Parameters{sqlParameter}
|
||||||
|
|
||||||
|
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, params, nil)
|
||||||
|
|
||||||
|
// finish tool setup
|
||||||
|
t := Tool{
|
||||||
|
Config: cfg,
|
||||||
|
Parameters: params,
|
||||||
|
manifest: tools.Manifest{Description: cfg.Description, Parameters: params.Manifest(), AuthRequired: cfg.AuthRequired},
|
||||||
|
mcpManifest: mcpManifest,
|
||||||
|
}
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate interface
|
||||||
|
var _ tools.Tool = Tool{}
|
||||||
|
|
||||||
|
type Tool struct {
|
||||||
|
Config
|
||||||
|
Parameters parameters.Parameters `yaml:"parameters"`
|
||||||
|
manifest tools.Manifest
|
||||||
|
mcpManifest tools.McpManifest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, params parameters.ParamValues, accessToken tools.AccessToken) (any, error) {
|
||||||
|
source, err := tools.GetCompatibleSource[compatibleSource](resourceMgr, t.Source, t.Name, t.Kind)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mapParams := params.AsMap()
|
||||||
|
sql, ok := mapParams["sql"].(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid parameters: sql parameter is not a string")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log the query executed for debugging.
|
||||||
|
logger, err := util.LoggerFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error getting logger: %s", err)
|
||||||
|
}
|
||||||
|
logger.DebugContext(ctx, fmt.Sprintf("executing `%s` tool query: %s", kind, sql))
|
||||||
|
return source.RunSQL(ctx, sql, nil)
|
||||||
|
|
||||||
|
}
|
||||||
|
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (parameters.ParamValues, error) {
|
||||||
|
return parameters.ParseParams(t.Parameters, data, claims)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tool) EmbedParams(ctx context.Context, paramValues parameters.ParamValues, embeddingModelsMap map[string]embeddingmodels.EmbeddingModel) (parameters.ParamValues, error) {
|
||||||
|
return parameters.EmbedParams(ctx, t.Parameters, paramValues, embeddingModelsMap, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tool) Manifest() tools.Manifest {
|
||||||
|
return t.manifest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tool) McpManifest() tools.McpManifest {
|
||||||
|
return t.mcpManifest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tool) Authorized(verifiedAuthServices []string) bool {
|
||||||
|
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tool) RequiresClientAuthorization(resourceMgr tools.SourceProvider) (bool, error) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tool) ToConfig() tools.ToolConfig {
|
||||||
|
return t.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tool) GetAuthTokenHeaderName(resourceMgr tools.SourceProvider) (string, error) {
|
||||||
|
return "Authorization", nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
// Copyright 2026 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package snowflakeexecutesql_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
yaml "github.com/goccy/go-yaml"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/server"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/tools/snowflake/snowflakeexecutesql"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseFromYaml(t *testing.T) {
|
||||||
|
ctx, err := testutils.ContextWithNewLogger()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
tcs := []struct {
|
||||||
|
desc string
|
||||||
|
in string
|
||||||
|
want server.ToolConfigs
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "basic example",
|
||||||
|
in: `
|
||||||
|
tools:
|
||||||
|
my-snowflake-tool:
|
||||||
|
kind: snowflake-execute-sql
|
||||||
|
source: my-snowflake-source
|
||||||
|
description: Execute SQL on Snowflake
|
||||||
|
`,
|
||||||
|
want: server.ToolConfigs{
|
||||||
|
"my-snowflake-tool": snowflakeexecutesql.Config{
|
||||||
|
Name: "my-snowflake-tool",
|
||||||
|
Kind: "snowflake-execute-sql",
|
||||||
|
Source: "my-snowflake-source",
|
||||||
|
Description: "Execute SQL on Snowflake",
|
||||||
|
AuthRequired: []string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tcs {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
got := struct {
|
||||||
|
Tools server.ToolConfigs `yaml:"tools"`
|
||||||
|
}{}
|
||||||
|
// Parse contents
|
||||||
|
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to unmarshal: %s", err)
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
|
||||||
|
t.Fatalf("incorrect parse: diff %v", diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFailParseFromYaml(t *testing.T) {
|
||||||
|
ctx, err := testutils.ContextWithNewLogger()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
tcs := []struct {
|
||||||
|
desc string
|
||||||
|
in string
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "missing required field",
|
||||||
|
in: `
|
||||||
|
tools:
|
||||||
|
my-snowflake-tool:
|
||||||
|
kind: snowflake-execute-sql
|
||||||
|
source: my-snowflake-source
|
||||||
|
`,
|
||||||
|
err: "unable to parse tool \"my-snowflake-tool\" as kind \"snowflake-execute-sql\": Key: 'Config.Description' Error:Field validation for 'Description' failed on the 'required' tag",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tcs {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
got := struct {
|
||||||
|
Tools server.ToolConfigs `yaml:"tools"`
|
||||||
|
}{}
|
||||||
|
// Parse contents
|
||||||
|
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expect parsing to fail")
|
||||||
|
}
|
||||||
|
errStr := err.Error()
|
||||||
|
if errStr != tc.err {
|
||||||
|
t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
147
internal/tools/snowflake/snowflakesql/snowflakesql.go
Normal file
147
internal/tools/snowflake/snowflakesql/snowflakesql.go
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
// Copyright 2026 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package snowflakesql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
yaml "github.com/goccy/go-yaml"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/embeddingmodels"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/util/parameters"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
const kind string = "snowflake-sql"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if !tools.Register(kind, newConfig) {
|
||||||
|
panic(fmt.Sprintf("tool kind %q already registered", kind))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
|
||||||
|
actual := Config{Name: name}
|
||||||
|
if err := decoder.DecodeContext(ctx, &actual); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return actual, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type compatibleSource interface {
|
||||||
|
SnowflakeDB() *sqlx.DB
|
||||||
|
RunSQL(context.Context, string, []any) (any, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Name string `yaml:"name" validate:"required"`
|
||||||
|
Kind string `yaml:"kind" validate:"required"`
|
||||||
|
Source string `yaml:"source" validate:"required"`
|
||||||
|
Description string `yaml:"description" validate:"required"`
|
||||||
|
Statement string `yaml:"statement" validate:"required"`
|
||||||
|
AuthRequired []string `yaml:"authRequired"`
|
||||||
|
Parameters parameters.Parameters `yaml:"parameters"`
|
||||||
|
TemplateParameters parameters.Parameters `yaml:"templateParameters"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate interface
|
||||||
|
var _ tools.ToolConfig = Config{}
|
||||||
|
|
||||||
|
func (cfg Config) ToolConfigKind() string {
|
||||||
|
return kind
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
|
||||||
|
allParameters, paramManifest, err := parameters.ProcessParameters(cfg.TemplateParameters, cfg.Parameters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters, nil)
|
||||||
|
|
||||||
|
// finish tool setup
|
||||||
|
t := Tool{
|
||||||
|
Config: cfg,
|
||||||
|
AllParams: allParameters,
|
||||||
|
manifest: tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired},
|
||||||
|
mcpManifest: mcpManifest,
|
||||||
|
}
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate interface
|
||||||
|
var _ tools.Tool = Tool{}
|
||||||
|
|
||||||
|
type Tool struct {
|
||||||
|
Config
|
||||||
|
AllParams parameters.Parameters `yaml:"allParams"`
|
||||||
|
manifest tools.Manifest
|
||||||
|
mcpManifest tools.McpManifest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, params parameters.ParamValues, accessToken tools.AccessToken) (any, error) {
|
||||||
|
source, err := tools.GetCompatibleSource[compatibleSource](resourceMgr, t.Source, t.Name, t.Kind)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
paramsMap := params.AsMap()
|
||||||
|
newStatement, err := parameters.ResolveTemplateParams(t.TemplateParameters, t.Statement, paramsMap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to extract template params %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newParams, err := parameters.GetParams(t.Parameters, paramsMap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to extract standard params %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sliceParams := newParams.AsSlice()
|
||||||
|
return source.RunSQL(ctx, newStatement, sliceParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (parameters.ParamValues, error) {
|
||||||
|
return parameters.ParseParams(t.AllParams, data, claims)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tool) EmbedParams(ctx context.Context, paramValues parameters.ParamValues, embeddingModelsMap map[string]embeddingmodels.EmbeddingModel) (parameters.ParamValues, error) {
|
||||||
|
return parameters.EmbedParams(ctx, t.AllParams, paramValues, embeddingModelsMap, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tool) Manifest() tools.Manifest {
|
||||||
|
return t.manifest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tool) McpManifest() tools.McpManifest {
|
||||||
|
return t.mcpManifest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tool) Authorized(verifiedAuthServices []string) bool {
|
||||||
|
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tool) RequiresClientAuthorization(resourceMgr tools.SourceProvider) (bool, error) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tool) ToConfig() tools.ToolConfig {
|
||||||
|
return t.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tool) GetAuthTokenHeaderName(resourceMgr tools.SourceProvider) (string, error) {
|
||||||
|
return "Authorization", nil
|
||||||
|
}
|
||||||
116
internal/tools/snowflake/snowflakesql/snowflakesql_test.go
Normal file
116
internal/tools/snowflake/snowflakesql/snowflakesql_test.go
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
// Copyright 2026 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package snowflakesql_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
yaml "github.com/goccy/go-yaml"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/server"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/tools/snowflake/snowflakesql"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseFromYaml(t *testing.T) {
|
||||||
|
ctx, err := testutils.ContextWithNewLogger()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
tcs := []struct {
|
||||||
|
desc string
|
||||||
|
in string
|
||||||
|
want server.ToolConfigs
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "basic example",
|
||||||
|
in: `
|
||||||
|
tools:
|
||||||
|
my-snowflake-tool:
|
||||||
|
kind: snowflake-sql
|
||||||
|
source: my-snowflake-source
|
||||||
|
description: Execute parameterized SQL on Snowflake
|
||||||
|
statement: SELECT * FROM my_table WHERE id = $1
|
||||||
|
`,
|
||||||
|
want: server.ToolConfigs{
|
||||||
|
"my-snowflake-tool": snowflakesql.Config{
|
||||||
|
Name: "my-snowflake-tool",
|
||||||
|
Kind: "snowflake-sql",
|
||||||
|
Source: "my-snowflake-source",
|
||||||
|
Description: "Execute parameterized SQL on Snowflake",
|
||||||
|
Statement: "SELECT * FROM my_table WHERE id = $1",
|
||||||
|
AuthRequired: []string{},
|
||||||
|
Parameters: nil,
|
||||||
|
TemplateParameters: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tcs {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
got := struct {
|
||||||
|
Tools server.ToolConfigs `yaml:"tools"`
|
||||||
|
}{}
|
||||||
|
// Parse contents
|
||||||
|
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to unmarshal: %s", err)
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
|
||||||
|
t.Fatalf("incorrect parse: diff %v", diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFailParseFromYaml(t *testing.T) {
|
||||||
|
ctx, err := testutils.ContextWithNewLogger()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
tcs := []struct {
|
||||||
|
desc string
|
||||||
|
in string
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "missing required field",
|
||||||
|
in: `
|
||||||
|
tools:
|
||||||
|
my-snowflake-tool:
|
||||||
|
kind: snowflake-sql
|
||||||
|
source: my-snowflake-source
|
||||||
|
description: Execute parameterized SQL on Snowflake
|
||||||
|
`,
|
||||||
|
err: "unable to parse tool \"my-snowflake-tool\" as kind \"snowflake-sql\": Key: 'Config.Statement' Error:Field validation for 'Statement' failed on the 'required' tag",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tcs {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
got := struct {
|
||||||
|
Tools server.ToolConfigs `yaml:"tools"`
|
||||||
|
}{}
|
||||||
|
// Parse contents
|
||||||
|
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expect parsing to fail")
|
||||||
|
}
|
||||||
|
errStr := err.Error()
|
||||||
|
if errStr != tc.err {
|
||||||
|
t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,233 @@
|
|||||||
|
// Copyright 2026 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package spannercreateinstance
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
instance "cloud.google.com/go/spanner/admin/instance/apiv1"
|
||||||
|
"cloud.google.com/go/spanner/admin/instance/apiv1/instancepb"
|
||||||
|
"github.com/goccy/go-yaml"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/embeddingmodels"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/util/parameters"
|
||||||
|
)
|
||||||
|
|
||||||
|
const kind string = "spanner-create-instance"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if !tools.Register(kind, newConfig) {
|
||||||
|
panic(fmt.Sprintf("tool kind %q already registered", kind))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
|
||||||
|
actual := Config{Name: name}
|
||||||
|
if err := decoder.DecodeContext(ctx, &actual); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return actual, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type compatibleSource interface {
|
||||||
|
GetDefaultProject() string
|
||||||
|
GetClient(context.Context, string) (*instance.InstanceAdminClient, error)
|
||||||
|
UseClientAuthorization() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config defines the configuration for the create-instance tool.
|
||||||
|
type Config struct {
|
||||||
|
Name string `yaml:"name" validate:"required"`
|
||||||
|
Kind string `yaml:"kind" validate:"required"`
|
||||||
|
Description string `yaml:"description"`
|
||||||
|
Source string `yaml:"source" validate:"required"`
|
||||||
|
AuthRequired []string `yaml:"authRequired"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate interface
|
||||||
|
var _ tools.ToolConfig = Config{}
|
||||||
|
|
||||||
|
// ToolConfigKind returns the kind of the tool.
|
||||||
|
func (cfg Config) ToolConfigKind() string {
|
||||||
|
return kind
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize initializes the tool from the configuration.
|
||||||
|
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
|
||||||
|
rawS, ok := srcs[cfg.Source]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("no source named %q configured", cfg.Source)
|
||||||
|
}
|
||||||
|
s, ok := rawS.(compatibleSource)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid source for %q tool: source %q not compatible", kind, cfg.Source)
|
||||||
|
}
|
||||||
|
|
||||||
|
project := s.GetDefaultProject()
|
||||||
|
var projectParam parameters.Parameter
|
||||||
|
if project != "" {
|
||||||
|
projectParam = parameters.NewStringParameterWithDefault("project", project, "The GCP project ID.")
|
||||||
|
} else {
|
||||||
|
projectParam = parameters.NewStringParameter("project", "The project ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
allParameters := parameters.Parameters{
|
||||||
|
projectParam,
|
||||||
|
parameters.NewStringParameter("instanceId", "The ID of the instance"),
|
||||||
|
parameters.NewStringParameter("displayName", "The display name of the instance"),
|
||||||
|
parameters.NewStringParameter("config", "The instance configuration (e.g., regional-us-central1)"),
|
||||||
|
parameters.NewIntParameter("nodeCount", "The number of nodes, mutually exclusive with processingUnits (one must be 0)"),
|
||||||
|
parameters.NewIntParameter("processingUnits", "The number of processing units, mutually exclusive with nodeCount (one must be 0)"),
|
||||||
|
parameters.NewStringParameter("edition", "The edition of the instance (STANDARD, ENTERPRISE, ENTERPRISE_PLUS)"),
|
||||||
|
}
|
||||||
|
|
||||||
|
paramManifest := allParameters.Manifest()
|
||||||
|
|
||||||
|
description := cfg.Description
|
||||||
|
if description == "" {
|
||||||
|
description = "Creates a Spanner instance."
|
||||||
|
}
|
||||||
|
mcpManifest := tools.GetMcpManifest(cfg.Name, description, cfg.AuthRequired, allParameters, nil)
|
||||||
|
|
||||||
|
return Tool{
|
||||||
|
Config: cfg,
|
||||||
|
AllParams: allParameters,
|
||||||
|
manifest: tools.Manifest{Description: description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired},
|
||||||
|
mcpManifest: mcpManifest,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tool represents the create-instance tool.
|
||||||
|
type Tool struct {
|
||||||
|
Config
|
||||||
|
AllParams parameters.Parameters
|
||||||
|
manifest tools.Manifest
|
||||||
|
mcpManifest tools.McpManifest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tool) ToConfig() tools.ToolConfig {
|
||||||
|
return t.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoke executes the tool's logic.
|
||||||
|
func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, params parameters.ParamValues, accessToken tools.AccessToken) (any, error) {
|
||||||
|
paramsMap := params.AsMap()
|
||||||
|
|
||||||
|
project, _ := paramsMap["project"].(string)
|
||||||
|
instanceId, _ := paramsMap["instanceId"].(string)
|
||||||
|
displayName, _ := paramsMap["displayName"].(string)
|
||||||
|
config, _ := paramsMap["config"].(string)
|
||||||
|
nodeCount, _ := paramsMap["nodeCount"].(int)
|
||||||
|
processingUnits, _ := paramsMap["processingUnits"].(int)
|
||||||
|
editionStr, _ := paramsMap["edition"].(string)
|
||||||
|
|
||||||
|
if (nodeCount > 0 && processingUnits > 0) || (nodeCount == 0 && processingUnits == 0) {
|
||||||
|
return nil, fmt.Errorf("one of nodeCount or processingUnits must be positive, and the other must be 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
source, err := tools.GetCompatibleSource[compatibleSource](resourceMgr, t.Source, t.Name, t.Kind)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := source.GetClient(ctx, string(accessToken))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if source.UseClientAuthorization() {
|
||||||
|
defer client.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
parent := fmt.Sprintf("projects/%s", project)
|
||||||
|
instanceConfig := fmt.Sprintf("projects/%s/instanceConfigs/%s", project, config)
|
||||||
|
|
||||||
|
var edition instancepb.Instance_Edition
|
||||||
|
switch editionStr {
|
||||||
|
case "STANDARD":
|
||||||
|
edition = instancepb.Instance_STANDARD
|
||||||
|
case "ENTERPRISE":
|
||||||
|
edition = instancepb.Instance_ENTERPRISE
|
||||||
|
case "ENTERPRISE_PLUS":
|
||||||
|
edition = instancepb.Instance_ENTERPRISE_PLUS
|
||||||
|
default:
|
||||||
|
edition = instancepb.Instance_EDITION_UNSPECIFIED
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct the instance object
|
||||||
|
instance := &instancepb.Instance{
|
||||||
|
Config: instanceConfig,
|
||||||
|
DisplayName: displayName,
|
||||||
|
Edition: edition,
|
||||||
|
NodeCount: int32(nodeCount),
|
||||||
|
ProcessingUnits: int32(processingUnits),
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &instancepb.CreateInstanceRequest{
|
||||||
|
Parent: parent,
|
||||||
|
InstanceId: instanceId,
|
||||||
|
Instance: instance,
|
||||||
|
}
|
||||||
|
|
||||||
|
op, err := client.CreateInstance(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create instance: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the operation to complete
|
||||||
|
resp, err := op.Wait(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to wait for create instance operation: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseParams parses the parameters for the tool.
|
||||||
|
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (parameters.ParamValues, error) {
|
||||||
|
return parameters.ParseParams(t.AllParams, data, claims)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tool) EmbedParams(ctx context.Context, paramValues parameters.ParamValues, embeddingModelsMap map[string]embeddingmodels.EmbeddingModel) (parameters.ParamValues, error) {
|
||||||
|
return parameters.EmbedParams(ctx, t.AllParams, paramValues, embeddingModelsMap, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manifest returns the tool's manifest.
|
||||||
|
func (t Tool) Manifest() tools.Manifest {
|
||||||
|
return t.manifest
|
||||||
|
}
|
||||||
|
|
||||||
|
// McpManifest returns the tool's MCP manifest.
|
||||||
|
func (t Tool) McpManifest() tools.McpManifest {
|
||||||
|
return t.mcpManifest
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authorized checks if the tool is authorized.
|
||||||
|
func (t Tool) Authorized(verifiedAuthServices []string) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tool) RequiresClientAuthorization(resourceMgr tools.SourceProvider) (bool, error) {
|
||||||
|
source, err := tools.GetCompatibleSource[compatibleSource](resourceMgr, t.Source, t.Name, t.Kind)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return source.UseClientAuthorization(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tool) GetAuthTokenHeaderName(resourceMgr tools.SourceProvider) (string, error) {
|
||||||
|
return "Authorization", nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
// Copyright 2026 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package spannercreateinstance_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
yaml "github.com/goccy/go-yaml"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/server"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/tools/spanneradmin/spannercreateinstance"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/util/parameters"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseFromYaml(t *testing.T) {
|
||||||
|
ctx, err := testutils.ContextWithNewLogger()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
tcs := []struct {
|
||||||
|
desc string
|
||||||
|
in string
|
||||||
|
want server.ToolConfigs
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "basic example",
|
||||||
|
in: `
|
||||||
|
tools:
|
||||||
|
create-instance-tool:
|
||||||
|
kind: spanner-create-instance
|
||||||
|
description: a test description
|
||||||
|
source: a-source
|
||||||
|
`,
|
||||||
|
want: server.ToolConfigs{
|
||||||
|
"create-instance-tool": spannercreateinstance.Config{
|
||||||
|
Name: "create-instance-tool",
|
||||||
|
Kind: "spanner-create-instance",
|
||||||
|
Description: "a test description",
|
||||||
|
Source: "a-source",
|
||||||
|
AuthRequired: []string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tcs {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
got := struct {
|
||||||
|
Tools server.ToolConfigs `yaml:"tools"`
|
||||||
|
}{}
|
||||||
|
// Parse contents
|
||||||
|
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to unmarshal: %s", err)
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
|
||||||
|
t.Fatalf("incorrect parse: diff %v", diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInvokeNodeCountAndProcessingUnitsValidation(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
params parameters.ParamValues
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Both positive",
|
||||||
|
params: parameters.ParamValues{
|
||||||
|
{Name: "nodeCount", Value: 1},
|
||||||
|
{Name: "processingUnits", Value: 1000},
|
||||||
|
},
|
||||||
|
wantErr: "one of nodeCount or processingUnits must be positive, and the other must be 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Both zero",
|
||||||
|
params: parameters.ParamValues{
|
||||||
|
{Name: "nodeCount", Value: 0},
|
||||||
|
{Name: "processingUnits", Value: 0},
|
||||||
|
},
|
||||||
|
wantErr: "one of nodeCount or processingUnits must be positive, and the other must be 0",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
tool := spannercreateinstance.Tool{}
|
||||||
|
_, err := tool.Invoke(context.Background(), nil, tc.params, "")
|
||||||
|
if err == nil || err.Error() != tc.wantErr {
|
||||||
|
t.Errorf("Invoke() error = %v, wantErr %v", err, tc.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -478,9 +478,13 @@ func (ps Parameters) McpManifest() (McpToolsSchema, map[string][]string) {
|
|||||||
for _, p := range ps {
|
for _, p := range ps {
|
||||||
name := p.GetName()
|
name := p.GetName()
|
||||||
paramManifest, authParamList := p.McpManifest()
|
paramManifest, authParamList := p.McpManifest()
|
||||||
|
defaultV := p.GetDefault()
|
||||||
|
if defaultV != nil {
|
||||||
|
paramManifest.Default = defaultV
|
||||||
|
}
|
||||||
properties[name] = paramManifest
|
properties[name] = paramManifest
|
||||||
// parameters that doesn't have a default value are added to the required field
|
// parameters that doesn't have a default value are added to the required field
|
||||||
if CheckParamRequired(p.GetRequired(), p.GetDefault()) {
|
if CheckParamRequired(p.GetRequired(), defaultV) {
|
||||||
required = append(required, name)
|
required = append(required, name)
|
||||||
}
|
}
|
||||||
if len(authParamList) > 0 {
|
if len(authParamList) > 0 {
|
||||||
@@ -502,6 +506,7 @@ type ParameterManifest struct {
|
|||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
AuthServices []string `json:"authSources"`
|
AuthServices []string `json:"authSources"`
|
||||||
Items *ParameterManifest `json:"items,omitempty"`
|
Items *ParameterManifest `json:"items,omitempty"`
|
||||||
|
Default any `json:"default,omitempty"`
|
||||||
AdditionalProperties any `json:"additionalProperties,omitempty"`
|
AdditionalProperties any `json:"additionalProperties,omitempty"`
|
||||||
EmbeddedBy string `json:"embeddedBy,omitempty"`
|
EmbeddedBy string `json:"embeddedBy,omitempty"`
|
||||||
}
|
}
|
||||||
@@ -511,6 +516,7 @@ type ParameterMcpManifest struct {
|
|||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Items *ParameterMcpManifest `json:"items,omitempty"`
|
Items *ParameterMcpManifest `json:"items,omitempty"`
|
||||||
|
Default any `json:"default,omitempty"`
|
||||||
AdditionalProperties any `json:"additionalProperties,omitempty"`
|
AdditionalProperties any `json:"additionalProperties,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -788,6 +794,7 @@ func (p *StringParameter) Manifest() ParameterManifest {
|
|||||||
Required: r,
|
Required: r,
|
||||||
Description: p.Desc,
|
Description: p.Desc,
|
||||||
AuthServices: authServiceNames,
|
AuthServices: authServiceNames,
|
||||||
|
Default: p.GetDefault(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -946,6 +953,7 @@ func (p *IntParameter) Manifest() ParameterManifest {
|
|||||||
Required: r,
|
Required: r,
|
||||||
Description: p.Desc,
|
Description: p.Desc,
|
||||||
AuthServices: authServiceNames,
|
AuthServices: authServiceNames,
|
||||||
|
Default: p.GetDefault(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1102,6 +1110,7 @@ func (p *FloatParameter) Manifest() ParameterManifest {
|
|||||||
Required: r,
|
Required: r,
|
||||||
Description: p.Desc,
|
Description: p.Desc,
|
||||||
AuthServices: authServiceNames,
|
AuthServices: authServiceNames,
|
||||||
|
Default: p.GetDefault(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1235,6 +1244,7 @@ func (p *BooleanParameter) Manifest() ParameterManifest {
|
|||||||
Required: r,
|
Required: r,
|
||||||
Description: p.Desc,
|
Description: p.Desc,
|
||||||
AuthServices: authServiceNames,
|
AuthServices: authServiceNames,
|
||||||
|
Default: p.GetDefault(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1430,6 +1440,7 @@ func (p *ArrayParameter) Manifest() ParameterManifest {
|
|||||||
Description: p.Desc,
|
Description: p.Desc,
|
||||||
AuthServices: authServiceNames,
|
AuthServices: authServiceNames,
|
||||||
Items: &items,
|
Items: &items,
|
||||||
|
Default: p.GetDefault(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1675,7 +1686,10 @@ func (p *MapParameter) Manifest() ParameterManifest {
|
|||||||
// If no valueType is given, allow any properties.
|
// If no valueType is given, allow any properties.
|
||||||
additionalProperties = true
|
additionalProperties = true
|
||||||
}
|
}
|
||||||
|
var defaultV any
|
||||||
|
if p.Default != nil {
|
||||||
|
defaultV = *p.Default
|
||||||
|
}
|
||||||
return ParameterManifest{
|
return ParameterManifest{
|
||||||
Name: p.Name,
|
Name: p.Name,
|
||||||
Type: "object",
|
Type: "object",
|
||||||
@@ -1683,6 +1697,7 @@ func (p *MapParameter) Manifest() ParameterManifest {
|
|||||||
Description: p.Desc,
|
Description: p.Desc,
|
||||||
AuthServices: authServiceNames,
|
AuthServices: authServiceNames,
|
||||||
AdditionalProperties: additionalProperties,
|
AdditionalProperties: additionalProperties,
|
||||||
|
Default: defaultV,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1625,22 +1625,22 @@ func TestParamManifest(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "string default",
|
name: "string default",
|
||||||
in: parameters.NewStringParameterWithDefault("foo-string", "foo", "bar"),
|
in: parameters.NewStringParameterWithDefault("foo-string", "foo", "bar"),
|
||||||
want: parameters.ParameterManifest{Name: "foo-string", Type: "string", Required: false, Description: "bar", AuthServices: []string{}},
|
want: parameters.ParameterManifest{Name: "foo-string", Type: "string", Required: false, Description: "bar", Default: "foo", AuthServices: []string{}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "int default",
|
name: "int default",
|
||||||
in: parameters.NewIntParameterWithDefault("foo-int", 1, "bar"),
|
in: parameters.NewIntParameterWithDefault("foo-int", 1, "bar"),
|
||||||
want: parameters.ParameterManifest{Name: "foo-int", Type: "integer", Required: false, Description: "bar", AuthServices: []string{}},
|
want: parameters.ParameterManifest{Name: "foo-int", Type: "integer", Required: false, Description: "bar", Default: 1, AuthServices: []string{}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "float default",
|
name: "float default",
|
||||||
in: parameters.NewFloatParameterWithDefault("foo-float", 1.1, "bar"),
|
in: parameters.NewFloatParameterWithDefault("foo-float", 1.1, "bar"),
|
||||||
want: parameters.ParameterManifest{Name: "foo-float", Type: "float", Required: false, Description: "bar", AuthServices: []string{}},
|
want: parameters.ParameterManifest{Name: "foo-float", Type: "float", Required: false, Description: "bar", Default: 1.1, AuthServices: []string{}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "boolean default",
|
name: "boolean default",
|
||||||
in: parameters.NewBooleanParameterWithDefault("foo-bool", true, "bar"),
|
in: parameters.NewBooleanParameterWithDefault("foo-bool", true, "bar"),
|
||||||
want: parameters.ParameterManifest{Name: "foo-bool", Type: "boolean", Required: false, Description: "bar", AuthServices: []string{}},
|
want: parameters.ParameterManifest{Name: "foo-bool", Type: "boolean", Required: false, Description: "bar", Default: true, AuthServices: []string{}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "array default",
|
name: "array default",
|
||||||
@@ -1650,6 +1650,7 @@ func TestParamManifest(t *testing.T) {
|
|||||||
Type: "array",
|
Type: "array",
|
||||||
Required: false,
|
Required: false,
|
||||||
Description: "bar",
|
Description: "bar",
|
||||||
|
Default: []any{"foo", "bar"},
|
||||||
AuthServices: []string{},
|
AuthServices: []string{},
|
||||||
Items: ¶meters.ParameterManifest{Name: "foo-string", Type: "string", Required: false, Description: "bar", AuthServices: []string{}},
|
Items: ¶meters.ParameterManifest{Name: "foo-string", Type: "string", Required: false, Description: "bar", AuthServices: []string{}},
|
||||||
},
|
},
|
||||||
@@ -1841,7 +1842,7 @@ func TestMcpManifest(t *testing.T) {
|
|||||||
wantSchema: parameters.McpToolsSchema{
|
wantSchema: parameters.McpToolsSchema{
|
||||||
Type: "object",
|
Type: "object",
|
||||||
Properties: map[string]parameters.ParameterMcpManifest{
|
Properties: map[string]parameters.ParameterMcpManifest{
|
||||||
"foo-string": {Type: "string", Description: "bar"},
|
"foo-string": {Type: "string", Description: "bar", Default: "foo"},
|
||||||
"foo-string2": {Type: "string", Description: "bar"},
|
"foo-string2": {Type: "string", Description: "bar"},
|
||||||
"foo-string3-auth": {Type: "string", Description: "bar"},
|
"foo-string3-auth": {Type: "string", Description: "bar"},
|
||||||
"foo-int2": {Type: "integer", Description: "bar"},
|
"foo-int2": {Type: "integer", Description: "bar"},
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
||||||
"name": "io.github.googleapis/genai-toolbox",
|
"name": "io.github.googleapis/genai-toolbox",
|
||||||
"description": "MCP Toolbox for Databases enables your agent to connect to your database.",
|
"description": "MCP Toolbox for Databases enables your agent to connect to your database.",
|
||||||
"title": "MCP Toolbox",
|
"title": "MCP Toolbox",
|
||||||
@@ -14,11 +14,11 @@
|
|||||||
"url": "https://github.com/googleapis/genai-toolbox",
|
"url": "https://github.com/googleapis/genai-toolbox",
|
||||||
"source": "github"
|
"source": "github"
|
||||||
},
|
},
|
||||||
"version": "0.24.0",
|
"version": "0.25.0",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"registryType": "oci",
|
"registryType": "oci",
|
||||||
"identifier": "us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:0.24.0",
|
"identifier": "us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:0.25.0",
|
||||||
"transport": {
|
"transport": {
|
||||||
"type": "streamable-http",
|
"type": "streamable-http",
|
||||||
"url": "http://{host}:{port}/mcp"
|
"url": "http://{host}:{port}/mcp"
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ func runAlloyDBToolGetTest(t *testing.T) {
|
|||||||
"description": "Simple tool to test end to end functionality.",
|
"description": "Simple tool to test end to end functionality.",
|
||||||
"parameters": []any{
|
"parameters": []any{
|
||||||
map[string]any{"name": "project", "type": "string", "description": "The GCP project ID to list clusters for.", "required": true, "authSources": []any{}},
|
map[string]any{"name": "project", "type": "string", "description": "The GCP project ID to list clusters for.", "required": true, "authSources": []any{}},
|
||||||
map[string]any{"name": "location", "type": "string", "description": "Optional: The location to list clusters in (e.g., 'us-central1'). Use '-' to list clusters across all locations.(Default: '-')", "required": false, "authSources": []any{}},
|
map[string]any{"name": "location", "type": "string", "description": "Optional: The location to list clusters in (e.g., 'us-central1'). Use '-' to list clusters across all locations.(Default: '-')", "required": false, "default": "-", "authSources": []any{}},
|
||||||
},
|
},
|
||||||
"authRequired": []any{},
|
"authRequired": []any{},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -431,6 +431,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"description": "The filters for the query",
|
"description": "The filters for the query",
|
||||||
"name": "filters",
|
"name": "filters",
|
||||||
"required": false,
|
"required": false,
|
||||||
|
"default": map[string]any{},
|
||||||
"type": "object",
|
"type": "object",
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
@@ -446,6 +447,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "pivots",
|
"name": "pivots",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "array",
|
"type": "array",
|
||||||
|
"default": []any{},
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"authSources": []any{},
|
"authSources": []any{},
|
||||||
@@ -460,6 +462,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "sorts",
|
"name": "sorts",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "array",
|
"type": "array",
|
||||||
|
"default": []any{},
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"authSources": []any{},
|
"authSources": []any{},
|
||||||
@@ -467,6 +470,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "limit",
|
"name": "limit",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
"default": float64(500),
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"authSources": []any{},
|
"authSources": []any{},
|
||||||
@@ -519,6 +523,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"description": "The filters for the query",
|
"description": "The filters for the query",
|
||||||
"name": "filters",
|
"name": "filters",
|
||||||
"required": false,
|
"required": false,
|
||||||
|
"default": map[string]any{},
|
||||||
"type": "object",
|
"type": "object",
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
@@ -534,6 +539,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "pivots",
|
"name": "pivots",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "array",
|
"type": "array",
|
||||||
|
"default": []any{},
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"authSources": []any{},
|
"authSources": []any{},
|
||||||
@@ -548,6 +554,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "sorts",
|
"name": "sorts",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "array",
|
"type": "array",
|
||||||
|
"default": []any{},
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"authSources": []any{},
|
"authSources": []any{},
|
||||||
@@ -555,6 +562,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "limit",
|
"name": "limit",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
"default": float64(500),
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"authSources": []any{},
|
"authSources": []any{},
|
||||||
@@ -607,6 +615,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"description": "The filters for the query",
|
"description": "The filters for the query",
|
||||||
"name": "filters",
|
"name": "filters",
|
||||||
"required": false,
|
"required": false,
|
||||||
|
"default": map[string]any{},
|
||||||
"type": "object",
|
"type": "object",
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
@@ -622,6 +631,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "pivots",
|
"name": "pivots",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "array",
|
"type": "array",
|
||||||
|
"default": []any{},
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"authSources": []any{},
|
"authSources": []any{},
|
||||||
@@ -636,6 +646,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "sorts",
|
"name": "sorts",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "array",
|
"type": "array",
|
||||||
|
"default": []any{},
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"authSources": []any{},
|
"authSources": []any{},
|
||||||
@@ -643,6 +654,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "limit",
|
"name": "limit",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
"default": float64(500),
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"authSources": []any{},
|
"authSources": []any{},
|
||||||
@@ -658,6 +670,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "vis_config",
|
"name": "vis_config",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"default": map[string]any{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -675,6 +688,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "title",
|
"name": "title",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"authSources": []any{},
|
"authSources": []any{},
|
||||||
@@ -682,6 +696,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "desc",
|
"name": "desc",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"authSources": []any{},
|
"authSources": []any{},
|
||||||
@@ -689,6 +704,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "limit",
|
"name": "limit",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
"default": float64(100),
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"authSources": []any{},
|
"authSources": []any{},
|
||||||
@@ -696,6 +712,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "offset",
|
"name": "offset",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
"default": float64(0),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -741,6 +758,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"description": "The filters for the query",
|
"description": "The filters for the query",
|
||||||
"name": "filters",
|
"name": "filters",
|
||||||
"required": false,
|
"required": false,
|
||||||
|
"default": map[string]any{},
|
||||||
"type": "object",
|
"type": "object",
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
@@ -756,6 +774,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "pivots",
|
"name": "pivots",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "array",
|
"type": "array",
|
||||||
|
"default": []any{},
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"authSources": []any{},
|
"authSources": []any{},
|
||||||
@@ -770,6 +789,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "sorts",
|
"name": "sorts",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "array",
|
"type": "array",
|
||||||
|
"default": []any{},
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"authSources": []any{},
|
"authSources": []any{},
|
||||||
@@ -777,6 +797,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "limit",
|
"name": "limit",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
"default": float64(500),
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"authSources": []any{},
|
"authSources": []any{},
|
||||||
@@ -797,6 +818,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"description": "The description of the Look",
|
"description": "The description of the Look",
|
||||||
"name": "description",
|
"name": "description",
|
||||||
"required": false,
|
"required": false,
|
||||||
|
"default": "",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
@@ -804,6 +826,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"description": "The folder id where the Look will be created. Leave blank to use the user's personal folder",
|
"description": "The folder id where the Look will be created. Leave blank to use the user's personal folder",
|
||||||
"name": "folder",
|
"name": "folder",
|
||||||
"required": false,
|
"required": false,
|
||||||
|
"default": "",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
@@ -813,6 +836,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "vis_config",
|
"name": "vis_config",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"default": map[string]any{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -830,6 +854,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "title",
|
"name": "title",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"authSources": []any{},
|
"authSources": []any{},
|
||||||
@@ -837,6 +862,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "desc",
|
"name": "desc",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"authSources": []any{},
|
"authSources": []any{},
|
||||||
@@ -844,6 +870,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "limit",
|
"name": "limit",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
"default": float64(100),
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"authSources": []any{},
|
"authSources": []any{},
|
||||||
@@ -851,6 +878,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "offset",
|
"name": "offset",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
"default": float64(0),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -875,6 +903,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "description",
|
"name": "description",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"authSources": []any{},
|
"authSources": []any{},
|
||||||
@@ -882,6 +911,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "folder",
|
"name": "folder",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -920,6 +950,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "filter_type",
|
"name": "filter_type",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"default": "field_filter",
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"authSources": []any{},
|
"authSources": []any{},
|
||||||
@@ -955,6 +986,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "allow_multiple_values",
|
"name": "allow_multiple_values",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"authSources": []any{},
|
"authSources": []any{},
|
||||||
@@ -962,6 +994,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "required",
|
"name": "required",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -1007,6 +1040,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"description": "The filters for the query",
|
"description": "The filters for the query",
|
||||||
"name": "filters",
|
"name": "filters",
|
||||||
"required": false,
|
"required": false,
|
||||||
|
"default": map[string]any{},
|
||||||
"type": "object",
|
"type": "object",
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
@@ -1022,6 +1056,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "pivots",
|
"name": "pivots",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "array",
|
"type": "array",
|
||||||
|
"default": []any{},
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"authSources": []any{},
|
"authSources": []any{},
|
||||||
@@ -1036,6 +1071,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "sorts",
|
"name": "sorts",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "array",
|
"type": "array",
|
||||||
|
"default": []any{},
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"authSources": []any{},
|
"authSources": []any{},
|
||||||
@@ -1043,6 +1079,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "limit",
|
"name": "limit",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
"default": float64(500),
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"authSources": []any{},
|
"authSources": []any{},
|
||||||
@@ -1064,6 +1101,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "title",
|
"name": "title",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"additionalProperties": true,
|
"additionalProperties": true,
|
||||||
@@ -1072,6 +1110,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "vis_config",
|
"name": "vis_config",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"default": map[string]any{},
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"authSources": []any{},
|
"authSources": []any{},
|
||||||
@@ -1083,6 +1122,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "dashboard_filter",
|
"name": "dashboard_filter",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"default": map[string]any{},
|
||||||
},
|
},
|
||||||
"name": "dashboard_filters",
|
"name": "dashboard_filters",
|
||||||
"required": false,
|
"required": false,
|
||||||
@@ -1181,6 +1221,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "timeframe",
|
"name": "timeframe",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
"default": float64(90),
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"authSources": []any{},
|
"authSources": []any{},
|
||||||
@@ -1188,6 +1229,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "min_queries",
|
"name": "min_queries",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
"default": float64(0),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -1212,6 +1254,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "project",
|
"name": "project",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"authSources": []any{},
|
"authSources": []any{},
|
||||||
@@ -1219,6 +1262,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "model",
|
"name": "model",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"authSources": []any{},
|
"authSources": []any{},
|
||||||
@@ -1226,6 +1270,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "explore",
|
"name": "explore",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"authSources": []any{},
|
"authSources": []any{},
|
||||||
@@ -1233,6 +1278,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "timeframe",
|
"name": "timeframe",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
"default": float64(90),
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"authSources": []any{},
|
"authSources": []any{},
|
||||||
@@ -1240,6 +1286,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "min_queries",
|
"name": "min_queries",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
"default": float64(1),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -1257,6 +1304,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "devMode",
|
"name": "devMode",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -1410,6 +1458,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "type",
|
"name": "type",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
},
|
},
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"authSources": []any{},
|
"authSources": []any{},
|
||||||
@@ -1417,6 +1466,7 @@ func TestLooker(t *testing.T) {
|
|||||||
"name": "id",
|
"name": "id",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -165,6 +165,7 @@ func TestNeo4jToolEndpoints(t *testing.T) {
|
|||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"required": false,
|
"required": false,
|
||||||
"description": "If set to true, the query will be validated and information about the execution will be returned without running the query. Defaults to false.",
|
"description": "If set to true, the query will be validated and information about the execution will be returned without running the query. Defaults to false.",
|
||||||
|
"default": false,
|
||||||
"authSources": []any{},
|
"authSources": []any{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -161,6 +161,9 @@ func WithMcpSelect1Want(want string) McpTestOption {
|
|||||||
// ExecuteSqlTestConfig represents the various configuration options for RunExecuteSqlToolInvokeTest()
|
// ExecuteSqlTestConfig represents the various configuration options for RunExecuteSqlToolInvokeTest()
|
||||||
type ExecuteSqlTestConfig struct {
|
type ExecuteSqlTestConfig struct {
|
||||||
select1Statement string
|
select1Statement string
|
||||||
|
createWant string
|
||||||
|
dropWant string
|
||||||
|
selectEmptyWant string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExecuteSqlOption func(*ExecuteSqlTestConfig)
|
type ExecuteSqlOption func(*ExecuteSqlTestConfig)
|
||||||
@@ -173,6 +176,27 @@ func WithSelect1Statement(s string) ExecuteSqlOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithExecuteCreateWant represents the expected response for a CREATE TABLE statement.
|
||||||
|
func WithExecuteCreateWant(s string) ExecuteSqlOption {
|
||||||
|
return func(c *ExecuteSqlTestConfig) {
|
||||||
|
c.createWant = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithExecuteDropWant represents the expected response for a DROP TABLE statement.
|
||||||
|
func WithExecuteDropWant(s string) ExecuteSqlOption {
|
||||||
|
return func(c *ExecuteSqlTestConfig) {
|
||||||
|
c.dropWant = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithExecuteSelectEmptyWant represents the expected response for a SELECT from an empty table.
|
||||||
|
func WithExecuteSelectEmptyWant(s string) ExecuteSqlOption {
|
||||||
|
return func(c *ExecuteSqlTestConfig) {
|
||||||
|
c.selectEmptyWant = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Configurations for RunToolInvokeWithTemplateParameters() */
|
/* Configurations for RunToolInvokeWithTemplateParameters() */
|
||||||
|
|
||||||
// TemplateParameterTestConfig represents the various configuration options for template parameter tests.
|
// TemplateParameterTestConfig represents the various configuration options for template parameter tests.
|
||||||
|
|||||||
257
tests/snowflake/snowflake_integration_test.go
Normal file
257
tests/snowflake/snowflake_integration_test.go
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
// Copyright 2026 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package snowflake
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||||
|
"github.com/googleapis/genai-toolbox/tests"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
_ "github.com/snowflakedb/gosnowflake"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
SnowflakeSourceKind = "snowflake"
|
||||||
|
SnowflakeToolKind = "snowflake-sql"
|
||||||
|
SnowflakeAccount = os.Getenv("SNOWFLAKE_ACCOUNT")
|
||||||
|
SnowflakeUser = os.Getenv("SNOWFLAKE_USER")
|
||||||
|
SnowflakePassword = os.Getenv("SNOWFLAKE_PASS")
|
||||||
|
SnowflakeDatabase = os.Getenv("SNOWFLAKE_DATABASE")
|
||||||
|
SnowflakeSchema = os.Getenv("SNOWFLAKE_SCHEMA")
|
||||||
|
SnowflakeWarehouse = os.Getenv("SNOWFLAKE_WAREHOUSE")
|
||||||
|
SnowflakeRole = os.Getenv("SNOWFLAKE_ROLE")
|
||||||
|
)
|
||||||
|
|
||||||
|
func getSnowflakeVars(t *testing.T) map[string]any {
|
||||||
|
switch "" {
|
||||||
|
case SnowflakeAccount:
|
||||||
|
t.Fatal("'SNOWFLAKE_ACCOUNT' not set")
|
||||||
|
case SnowflakeUser:
|
||||||
|
t.Fatal("'SNOWFLAKE_USER' not set")
|
||||||
|
case SnowflakePassword:
|
||||||
|
t.Fatal("'SNOWFLAKE_PASSWORD' not set")
|
||||||
|
case SnowflakeDatabase:
|
||||||
|
t.Fatal("'SNOWFLAKE_DATABASE' not set")
|
||||||
|
case SnowflakeSchema:
|
||||||
|
t.Fatal("'SNOWFLAKE_SCHEMA' not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set defaults for optional parameters
|
||||||
|
if SnowflakeWarehouse == "" {
|
||||||
|
SnowflakeWarehouse = "COMPUTE_WH"
|
||||||
|
}
|
||||||
|
if SnowflakeRole == "" {
|
||||||
|
SnowflakeRole = "ACCOUNTADMIN"
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]any{
|
||||||
|
"kind": SnowflakeSourceKind,
|
||||||
|
"account": SnowflakeAccount,
|
||||||
|
"user": SnowflakeUser,
|
||||||
|
"password": SnowflakePassword,
|
||||||
|
"database": SnowflakeDatabase,
|
||||||
|
"schema": SnowflakeSchema,
|
||||||
|
"warehouse": SnowflakeWarehouse,
|
||||||
|
"role": SnowflakeRole,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copied over from snowflake.go
|
||||||
|
func initSnowflakeConnectionPool(ctx context.Context, account, user, password, database, schema, warehouse, role string) (*sqlx.DB, error) {
|
||||||
|
// Set defaults for optional parameters
|
||||||
|
if warehouse == "" {
|
||||||
|
warehouse = "COMPUTE_WH"
|
||||||
|
}
|
||||||
|
if role == "" {
|
||||||
|
role = "ACCOUNTADMIN"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Snowflake DSN format: user:password@account/database/schema?warehouse=warehouse&role=role
|
||||||
|
dsn := fmt.Sprintf("%s:%s@%s/%s/%s?warehouse=%s&role=%s", user, password, account, database, schema, warehouse, role)
|
||||||
|
db, err := sqlx.ConnectContext(ctx, "snowflake", dsn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to create connection: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return db, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSnowflake(t *testing.T) {
|
||||||
|
sourceConfig := getSnowflakeVars(t)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var args []string
|
||||||
|
|
||||||
|
db, err := initSnowflakeConnectionPool(ctx, SnowflakeAccount, SnowflakeUser, SnowflakePassword, SnowflakeDatabase, SnowflakeSchema, SnowflakeWarehouse, SnowflakeRole)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create snowflake connection pool: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create table name with UUID
|
||||||
|
tableNameParam := "param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
|
||||||
|
tableNameAuth := "auth_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
|
||||||
|
tableNameTemplateParam := "template_param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
|
||||||
|
|
||||||
|
// set up data for param tool
|
||||||
|
createParamTableStmt, insertParamTableStmt, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, paramTestParams := getSnowflakeParamToolInfo(tableNameParam)
|
||||||
|
teardownTable1 := setupSnowflakeTable(t, ctx, db, createParamTableStmt, insertParamTableStmt, tableNameParam, paramTestParams)
|
||||||
|
defer teardownTable1(t)
|
||||||
|
|
||||||
|
// set up data for auth tool
|
||||||
|
createAuthTableStmt, insertAuthTableStmt, authToolStmt, authTestParams := getSnowflakeAuthToolInfo(tableNameAuth)
|
||||||
|
teardownTable2 := setupSnowflakeTable(t, ctx, db, createAuthTableStmt, insertAuthTableStmt, tableNameAuth, authTestParams)
|
||||||
|
defer teardownTable2(t)
|
||||||
|
t.Logf("Test table setup complete.")
|
||||||
|
|
||||||
|
// Write config into a file and pass it to command
|
||||||
|
toolsFile := tests.GetToolsConfig(sourceConfig, SnowflakeToolKind, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, authToolStmt)
|
||||||
|
toolsFile = addSnowflakeExecuteSqlConfig(t, toolsFile)
|
||||||
|
tmplSelectCombined, tmplSelectFilterCombined := getSnowflakeTmplToolStatement()
|
||||||
|
toolsFile = tests.AddTemplateParamConfig(t, toolsFile, SnowflakeToolKind, tmplSelectCombined, tmplSelectFilterCombined, "")
|
||||||
|
|
||||||
|
cmd, cleanup, err := tests.StartCmd(ctx, toolsFile, args...)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("command initialization returned an error: %s", err)
|
||||||
|
}
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
waitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
out, err := testutils.WaitForString(waitCtx, regexp.MustCompile(`Server ready to serve`), cmd.Out)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("toolbox command logs: \n%s", out)
|
||||||
|
t.Fatalf("toolbox didn't start successfully: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests.RunToolGetTest(t)
|
||||||
|
|
||||||
|
select1Want, failInvocationWant, createTableStatement, mcpSelect1Want := getSnowflakeWants()
|
||||||
|
|
||||||
|
// Run tests
|
||||||
|
tests.RunToolGetTest(t)
|
||||||
|
tests.RunToolInvokeTest(t, select1Want,
|
||||||
|
tests.DisableArrayTest(),
|
||||||
|
tests.WithMyToolId3NameAliceWant(`[{"ID":"1","NAME":"Alice"},{"ID":"3","NAME":"Sid"}]`),
|
||||||
|
tests.WithMyToolById4Want(`[{"ID":"4","NAME":null}]`),
|
||||||
|
tests.WithNullWant("null"),
|
||||||
|
)
|
||||||
|
tests.RunMCPToolCallMethod(t, failInvocationWant, mcpSelect1Want, tests.WithMcpMyToolId3NameAliceWant(`{"jsonrpc":"2.0","id":"my-tool","result":{"content":[{"type":"text","text":"{\"ID\":\"1\",\"NAME\":\"Alice\"}"},{"type":"text","text":"{\"ID\":\"3\",\"NAME\":\"Sid\"}"}]}}`))
|
||||||
|
|
||||||
|
tests.RunExecuteSqlToolInvokeTest(t, createTableStatement, select1Want,
|
||||||
|
tests.WithExecuteCreateWant(`[{"status":"Table T successfully created."}]`),
|
||||||
|
tests.WithExecuteDropWant(`[{"status":"T successfully dropped."}]`))
|
||||||
|
tests.RunToolInvokeWithTemplateParameters(t, tableNameTemplateParam)
|
||||||
|
}
|
||||||
|
|
||||||
|
// addSnowflakeExecuteSqlConfig gets the tools config for `snowflake-execute-sql`
|
||||||
|
func addSnowflakeExecuteSqlConfig(t *testing.T, config map[string]any) map[string]any {
|
||||||
|
|
||||||
|
tools, ok := config["tools"].(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("unable to get tools from config")
|
||||||
|
}
|
||||||
|
|
||||||
|
tools["my-exec-sql-tool"] = map[string]any{
|
||||||
|
"kind": "snowflake-execute-sql",
|
||||||
|
"source": "my-instance",
|
||||||
|
"description": "Tool to execute sql",
|
||||||
|
}
|
||||||
|
|
||||||
|
tools["my-auth-exec-sql-tool"] = map[string]any{
|
||||||
|
"kind": "snowflake-execute-sql",
|
||||||
|
"source": "my-instance",
|
||||||
|
"description": "Tool to execute sql",
|
||||||
|
"authRequired": []string{
|
||||||
|
"my-google-auth",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
config["tools"] = tools
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSnowflakeParamToolInfo returns statements and param for my-param-tool snowflake-sql kind
|
||||||
|
func getSnowflakeParamToolInfo(tableName string) (string, string, string, string, string, string, []any) {
|
||||||
|
createStatement := fmt.Sprintf("CREATE TABLE %s (id INTEGER AUTOINCREMENT PRIMARY KEY, name STRING);", tableName)
|
||||||
|
insertStatement := fmt.Sprintf("INSERT INTO %s (name) VALUES (?), (?), (?), (?);", tableName)
|
||||||
|
toolStatement := fmt.Sprintf("SELECT * FROM %s WHERE id = ? OR name = ?;", tableName)
|
||||||
|
idParamStatement := fmt.Sprintf("SELECT * FROM %s WHERE id = ?;", tableName)
|
||||||
|
nameParamStatement := fmt.Sprintf("SELECT * FROM %s WHERE name = ?;", tableName)
|
||||||
|
arrayToolStatement := fmt.Sprintf("SELECT * FROM %s WHERE id = ANY(?) AND name = ANY(?);", tableName)
|
||||||
|
params := []any{"Alice", "Jane", "Sid", nil}
|
||||||
|
return createStatement, insertStatement, toolStatement, idParamStatement, nameParamStatement, arrayToolStatement, params
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSnowflakeAuthToolInfo returns statements and param of my-auth-tool for snowflake-sql kind
|
||||||
|
func getSnowflakeAuthToolInfo(tableName string) (string, string, string, []any) {
|
||||||
|
createStatement := fmt.Sprintf("CREATE TABLE %s (id INTEGER AUTOINCREMENT PRIMARY KEY, name STRING, email STRING);", tableName)
|
||||||
|
insertStatement := fmt.Sprintf("INSERT INTO %s (name, email) VALUES (?, ?), (?, ?)", tableName)
|
||||||
|
toolStatement := fmt.Sprintf("SELECT name FROM %s WHERE email = ?;", tableName)
|
||||||
|
params := []any{"Alice", tests.ServiceAccountEmail, "Jane", "janedoe@gmail.com"}
|
||||||
|
return createStatement, insertStatement, toolStatement, params
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSnowflakeTmplToolStatement returns statements and param for template parameter test cases for snowflake-sql kind
|
||||||
|
func getSnowflakeTmplToolStatement() (string, string) {
|
||||||
|
tmplSelectCombined := "SELECT * FROM {{.tableName}} WHERE id = ?"
|
||||||
|
tmplSelectFilterCombined := "SELECT * FROM {{.tableName}} WHERE {{.columnFilter}} = ?"
|
||||||
|
return tmplSelectCombined, tmplSelectFilterCombined
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSnowflakeWants return the expected wants for snowflake
|
||||||
|
func getSnowflakeWants() (string, string, string, string) {
|
||||||
|
select1Want := `[{"1":"1"}]`
|
||||||
|
failInvocationWant := `unexpected 'SELEC'`
|
||||||
|
createTableStatement := `"CREATE TABLE t (id INTEGER AUTOINCREMENT PRIMARY KEY, name STRING)"`
|
||||||
|
mcpSelect1Want := `{"jsonrpc":"2.0","id":"invoke my-auth-required-tool","result":{"content":[{"type":"text","text":"{\"1\":\"1\"}"}]}}`
|
||||||
|
return select1Want, failInvocationWant, createTableStatement, mcpSelect1Want
|
||||||
|
}
|
||||||
|
|
||||||
|
// setupSnowflakeTable creates and inserts data into a table of tool
|
||||||
|
// compatible with snowflake-sql tool
|
||||||
|
func setupSnowflakeTable(t *testing.T, ctx context.Context, db *sqlx.DB, createStatement, insertStatement, tableName string, params []any) func(*testing.T) {
|
||||||
|
err := db.PingContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to connect to test database: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create table
|
||||||
|
_, err = db.QueryxContext(ctx, createStatement)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create test table %s: %s", tableName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert test data
|
||||||
|
_, err = db.QueryxContext(ctx, insertStatement, params...)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to insert test data: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(t *testing.T) {
|
||||||
|
// tear down test
|
||||||
|
_, err = db.ExecContext(ctx, fmt.Sprintf("DROP TABLE %s;", tableName))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Teardown failed: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
178
tests/spanneradmin/spanneradmin_integration_test.go
Normal file
178
tests/spanneradmin/spanneradmin_integration_test.go
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
// Copyright 2026 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package spanneradmin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
instance "cloud.google.com/go/spanner/admin/instance/apiv1"
|
||||||
|
"cloud.google.com/go/spanner/admin/instance/apiv1/instancepb"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||||
|
"github.com/googleapis/genai-toolbox/tests"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
SpannerProject = os.Getenv("SPANNER_PROJECT")
|
||||||
|
)
|
||||||
|
|
||||||
|
func getSpannerAdminVars(t *testing.T) map[string]any {
|
||||||
|
if SpannerProject == "" {
|
||||||
|
t.Fatal("'SPANNER_PROJECT' not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]any{
|
||||||
|
"kind": "spanner-admin",
|
||||||
|
"defaultProject": SpannerProject,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSpannerAdminCreateInstance(t *testing.T) {
|
||||||
|
sourceConfig := getSpannerAdminVars(t)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
shortUuid := strings.ReplaceAll(uuid.New().String(), "-", "")[:10]
|
||||||
|
instanceId := "test-inst-" + shortUuid
|
||||||
|
|
||||||
|
displayName := "Test Instance " + shortUuid
|
||||||
|
instanceConfig := "regional-us-central1"
|
||||||
|
nodeCount := 1
|
||||||
|
edition := "ENTERPRISE"
|
||||||
|
|
||||||
|
// Setup Admin Client for verification and cleanup
|
||||||
|
adminClient, err := instance.NewInstanceAdminClient(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create Spanner instance admin client: %s", err)
|
||||||
|
}
|
||||||
|
defer adminClient.Close()
|
||||||
|
|
||||||
|
// Teardown function
|
||||||
|
defer func() {
|
||||||
|
err := adminClient.DeleteInstance(ctx, &instancepb.DeleteInstanceRequest{
|
||||||
|
Name: fmt.Sprintf("projects/%s/instances/%s", SpannerProject, instanceId),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
// If it fails, it might not have been created, log it but don't fail if it's "not found"
|
||||||
|
t.Logf("cleanup: failed to delete instance %s: %s", instanceId, err)
|
||||||
|
} else {
|
||||||
|
t.Logf("cleanup: deleted instance %s", instanceId)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Construct Tools Config
|
||||||
|
|
||||||
|
toolsConfig := map[string]any{
|
||||||
|
"sources": map[string]any{
|
||||||
|
"my-spanner-admin": sourceConfig,
|
||||||
|
},
|
||||||
|
"tools": map[string]any{
|
||||||
|
"create-instance-tool": map[string]any{
|
||||||
|
"kind": "spanner-create-instance",
|
||||||
|
"source": "my-spanner-admin",
|
||||||
|
"description": "Creates a Spanner instance.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start Toolbox Server
|
||||||
|
cmd, cleanup, err := tests.StartCmd(ctx, toolsConfig)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("command initialization returned an error: %s", err)
|
||||||
|
}
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
waitCtx, cancelWait := context.WithTimeout(ctx, 10*time.Second)
|
||||||
|
defer cancelWait()
|
||||||
|
out, err := testutils.WaitForString(waitCtx, regexp.MustCompile(`Server ready to serve`), cmd.Out)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("toolbox command logs: \n%s", out)
|
||||||
|
t.Fatalf("toolbox didn't start successfully: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare Invocation Payload
|
||||||
|
|
||||||
|
payload := map[string]any{
|
||||||
|
"project": SpannerProject,
|
||||||
|
"instanceId": instanceId,
|
||||||
|
"displayName": displayName,
|
||||||
|
"config": instanceConfig,
|
||||||
|
"nodeCount": nodeCount,
|
||||||
|
"edition": edition,
|
||||||
|
"processingUnits": 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
payloadBytes, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to marshal payload: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoke Tool
|
||||||
|
invokeUrl := "http://127.0.0.1:5000/api/tool/create-instance-tool/invoke"
|
||||||
|
req, err := http.NewRequest(http.MethodPost, invokeUrl, bytes.NewBuffer(payloadBytes))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create request: %s", err)
|
||||||
|
}
|
||||||
|
req.Header.Add("Content-type", "application/json")
|
||||||
|
|
||||||
|
t.Logf("Invoking create-instance-tool for instance: %s", instanceId)
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to send request: %s", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||||
|
t.Fatalf("response status code is not 200, got %d: %s", resp.StatusCode, string(bodyBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check Response
|
||||||
|
var body map[string]interface{}
|
||||||
|
err = json.NewDecoder(resp.Body).Decode(&body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error parsing response body")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify Instance Exists via Admin Client
|
||||||
|
t.Logf("Verifying instance %s exists...", instanceId)
|
||||||
|
instanceName := fmt.Sprintf("projects/%s/instances/%s", SpannerProject, instanceId)
|
||||||
|
gotInstance, err := adminClient.GetInstance(ctx, &instancepb.GetInstanceRequest{
|
||||||
|
Name: instanceName,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get instance from admin client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if gotInstance.Name != instanceName {
|
||||||
|
t.Errorf("expected instance name %s, got %s", instanceName, gotInstance.Name)
|
||||||
|
}
|
||||||
|
if gotInstance.DisplayName != displayName {
|
||||||
|
t.Errorf("expected display name %s, got %s", displayName, gotInstance.DisplayName)
|
||||||
|
}
|
||||||
|
if gotInstance.NodeCount != int32(nodeCount) {
|
||||||
|
t.Errorf("expected node count %d, got %d", nodeCount, gotInstance.NodeCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -611,6 +611,9 @@ func RunExecuteSqlToolInvokeTest(t *testing.T, createTableStatement, select1Want
|
|||||||
// Default values for ExecuteSqlTestConfig
|
// Default values for ExecuteSqlTestConfig
|
||||||
configs := &ExecuteSqlTestConfig{
|
configs := &ExecuteSqlTestConfig{
|
||||||
select1Statement: `"SELECT 1"`,
|
select1Statement: `"SELECT 1"`,
|
||||||
|
createWant: "null",
|
||||||
|
dropWant: "null",
|
||||||
|
selectEmptyWant: "null",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply provided options
|
// Apply provided options
|
||||||
@@ -646,7 +649,7 @@ func RunExecuteSqlToolInvokeTest(t *testing.T, createTableStatement, select1Want
|
|||||||
api: "http://127.0.0.1:5000/api/tool/my-exec-sql-tool/invoke",
|
api: "http://127.0.0.1:5000/api/tool/my-exec-sql-tool/invoke",
|
||||||
requestHeader: map[string]string{},
|
requestHeader: map[string]string{},
|
||||||
requestBody: bytes.NewBuffer([]byte(fmt.Sprintf(`{"sql": %s}`, createTableStatement))),
|
requestBody: bytes.NewBuffer([]byte(fmt.Sprintf(`{"sql": %s}`, createTableStatement))),
|
||||||
want: "null",
|
want: configs.createWant,
|
||||||
isErr: false,
|
isErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -654,7 +657,7 @@ func RunExecuteSqlToolInvokeTest(t *testing.T, createTableStatement, select1Want
|
|||||||
api: "http://127.0.0.1:5000/api/tool/my-exec-sql-tool/invoke",
|
api: "http://127.0.0.1:5000/api/tool/my-exec-sql-tool/invoke",
|
||||||
requestHeader: map[string]string{},
|
requestHeader: map[string]string{},
|
||||||
requestBody: bytes.NewBuffer([]byte(`{"sql":"SELECT * FROM t"}`)),
|
requestBody: bytes.NewBuffer([]byte(`{"sql":"SELECT * FROM t"}`)),
|
||||||
want: "null",
|
want: configs.selectEmptyWant,
|
||||||
isErr: false,
|
isErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -662,7 +665,7 @@ func RunExecuteSqlToolInvokeTest(t *testing.T, createTableStatement, select1Want
|
|||||||
api: "http://127.0.0.1:5000/api/tool/my-exec-sql-tool/invoke",
|
api: "http://127.0.0.1:5000/api/tool/my-exec-sql-tool/invoke",
|
||||||
requestHeader: map[string]string{},
|
requestHeader: map[string]string{},
|
||||||
requestBody: bytes.NewBuffer([]byte(`{"sql":"DROP TABLE t"}`)),
|
requestBody: bytes.NewBuffer([]byte(`{"sql":"DROP TABLE t"}`)),
|
||||||
want: "null",
|
want: configs.dropWant,
|
||||||
isErr: false,
|
isErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user