mirror of
https://github.com/googleapis/genai-toolbox.git
synced 2026-02-18 19:05:19 -05:00
Compare commits
27 Commits
looker-dir
...
link-check
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b49f297a93 | ||
|
|
51a854209d | ||
|
|
a1bc7daf68 | ||
|
|
07c5e91fda | ||
|
|
62b64148bb | ||
|
|
47f5683745 | ||
|
|
e85b827b1e | ||
|
|
d22851388b | ||
|
|
bed9c66113 | ||
|
|
6191e2c203 | ||
|
|
99e84dd393 | ||
|
|
1b5d55b9ca | ||
|
|
81f7e479af | ||
|
|
600c8a3a8a | ||
|
|
17fe418aee | ||
|
|
a950b54fad | ||
|
|
df6f0818f7 | ||
|
|
58d38a07f0 | ||
|
|
636cfb7600 | ||
|
|
a0f511dc38 | ||
|
|
2fa1803fde | ||
|
|
4424086c94 | ||
|
|
538287097b | ||
|
|
5f11440639 | ||
|
|
bb2a23ff0e | ||
|
|
1c3c613860 | ||
|
|
c5bee2128e |
6
.github/release-please.yml
vendored
6
.github/release-please.yml
vendored
@@ -18,9 +18,9 @@ releaseType: simple
|
||||
versionFile: "cmd/version.txt"
|
||||
extraFiles: [
|
||||
"README.md",
|
||||
"docs/en/getting-started/colab_quickstart.ipynb",
|
||||
"docs/en/getting-started/introduction/_index.md",
|
||||
"docs/en/getting-started/mcp_quickstart/_index.md",
|
||||
"docs/en /getting-started/colab_quickstart.ipynb",
|
||||
"docs/en/getting-started/introduction /_index.md",
|
||||
"docs/en/getting-started/mcp_quickstart /_index.md",
|
||||
"docs/en/getting-started/quickstart/shared/configure_toolbox.md",
|
||||
"docs/en/samples/alloydb/_index.md",
|
||||
"docs/en/samples/alloydb/mcp_quickstart.md",
|
||||
|
||||
86
.github/workflows/link_checker_workflow.yaml
vendored
86
.github/workflows/link_checker_workflow.yaml
vendored
@@ -15,6 +15,10 @@ name: Link Checker
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
issues: write
|
||||
|
||||
|
||||
jobs:
|
||||
@@ -23,8 +27,36 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Identify Changed Files
|
||||
id: changed-files
|
||||
shell: bash
|
||||
run: |
|
||||
git fetch origin main
|
||||
CHANGED_FILES=$(git diff --name-only --diff-filter=ACMRT origin/main...HEAD)
|
||||
|
||||
if [ -z "$CHANGED_FILES" ]; then
|
||||
echo "No markdown files changed. Skipping checks."
|
||||
echo "HAS_CHANGES=false" >> $GITHUB_ENV
|
||||
else
|
||||
echo "--- Changed Files to Scan ---"
|
||||
echo "$CHANGED_FILES"
|
||||
echo "-----------------------------"
|
||||
|
||||
# FIX: Wrap filenames in quotes to handle spaces
|
||||
FILES_QUOTED=$(echo "$CHANGED_FILES" | sed 's/^/"/;s/$/"/' | tr '\n' ' ')
|
||||
|
||||
# Write to env using EOF pattern
|
||||
echo "CHECK_FILES<<EOF" >> $GITHUB_ENV
|
||||
echo "$FILES_QUOTED" >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
echo "HAS_CHANGES=true" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
|
||||
- name: Restore lychee cache
|
||||
if: env.HAS_CHANGES == 'true'
|
||||
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
|
||||
with:
|
||||
path: .lycheecache
|
||||
@@ -33,6 +65,7 @@ jobs:
|
||||
|
||||
- name: Link Checker
|
||||
id: lychee-check
|
||||
if: env.HAS_CHANGES == 'true'
|
||||
uses: lycheeverse/lychee-action@a8c4c7cb88f0c7386610c35eb25108e448569cb0 # v2
|
||||
continue-on-error: true
|
||||
with:
|
||||
@@ -42,8 +75,7 @@ jobs:
|
||||
--cache
|
||||
--max-cache-age 1d
|
||||
--exclude '^neo4j\+.*' --exclude '^bolt://.*'
|
||||
README.md
|
||||
docs/
|
||||
${{ env.CHECK_FILES }}
|
||||
output: lychee-report.md
|
||||
format: markdown
|
||||
fail: true
|
||||
@@ -51,18 +83,50 @@ jobs:
|
||||
debug: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Find comment
|
||||
uses: peter-evans/find-comment@v4
|
||||
id: find-comment
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-author: 'github-actions[bot]'
|
||||
body-includes: "## Link Resolution Note"
|
||||
|
||||
- name: Delete comment on success
|
||||
if: steps.lychee-check.outcome == 'success' && steps.find-comment.outputs.comment-id != ''
|
||||
run: |
|
||||
gh api \
|
||||
--method DELETE \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
/repos/${{ github.repository }}/issues/comments/${{ steps.find-comment.outputs.comment-id }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Prepare Report
|
||||
if: env.HAS_CHANGES == 'true' && steps.lychee-check.outcome == 'failure'
|
||||
run: |
|
||||
echo "## Link Resolution Note" > full-report.md
|
||||
|
||||
|
||||
echo "Local links and directory changes work differently on GitHub than on the docsite.You must ensure fixes pass the **GitHub check** and also work with **\`hugo server\`**." >> full-report.md
|
||||
echo "See [Link Checking and Fixing with Lychee](https://github.com/googleapis/genai-toolbox/blob/main/DEVELOPER.md#link-checking-and-fixing-with-lychee) for more details." >> full-report.md
|
||||
echo "" >> full-report.md
|
||||
sed -E '/(Redirect|Redirects per input)/d' lychee-report.md >> full-report.md
|
||||
|
||||
- name: Create PR Comment
|
||||
if: env.HAS_CHANGES == 'true' && steps.lychee-check.outcome == 'failure'
|
||||
uses: peter-evans/create-or-update-comment@v4
|
||||
with:
|
||||
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body-path: full-report.md
|
||||
edit-mode: replace
|
||||
|
||||
- name: Display Failure Report
|
||||
# Run this ONLY if the link checker failed
|
||||
if: steps.lychee-check.outcome == 'failure'
|
||||
run: |
|
||||
echo "## Link Resolution Note" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Local links and directory changes work differently on GitHub than on the docsite." >> $GITHUB_STEP_SUMMARY
|
||||
echo "You must ensure fixes pass the **GitHub check** and also work with **\`hugo server\`**." >> $GITHUB_STEP_SUMMARY
|
||||
echo "See [Link Checking and Fixing with Lychee](https://github.com/googleapis/genai-toolbox/blob/main/DEVELOPER.md#link-checking-and-fixing-with-lychee) for more details." >> $GITHUB_STEP_SUMMARY
|
||||
echo "---" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
echo "### Broken Links Found" >> $GITHUB_STEP_SUMMARY
|
||||
cat ./lychee-report.md >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# We can now simply output the prepared file to the job summary
|
||||
cat full-report.md >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Fail the job
|
||||
exit 1
|
||||
|
||||
@@ -105,9 +105,7 @@ import (
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookeradddashboardelement"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookeradddashboardfilter"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookerconversationalanalytics"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookercreateprojectdirectory"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookercreateprojectfile"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookerdeleteprojectdirectory"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookerdeleteprojectfile"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookerdevmode"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookergenerateembedurl"
|
||||
@@ -124,7 +122,6 @@ import (
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookergetmeasures"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookergetmodels"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookergetparameters"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookergetprojectdirectories"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookergetprojectfile"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookergetprojectfiles"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookergetprojects"
|
||||
|
||||
@@ -1778,7 +1778,7 @@ func TestPrebuiltTools(t *testing.T) {
|
||||
wantToolset: server.ToolsetConfigs{
|
||||
"looker_tools": tools.ToolsetConfig{
|
||||
Name: "looker_tools",
|
||||
ToolNames: []string{"get_models", "get_explores", "get_dimensions", "get_measures", "get_filters", "get_parameters", "query", "query_sql", "query_url", "get_looks", "run_look", "make_look", "get_dashboards", "run_dashboard", "make_dashboard", "add_dashboard_element", "add_dashboard_filter", "generate_embed_url", "health_pulse", "health_analyze", "health_vacuum", "dev_mode", "get_projects", "get_project_files", "get_project_file", "create_project_file", "update_project_file", "delete_project_file", "get_project_directories", "create_project_directory", "delete_project_directory", "validate_project", "get_connections", "get_connection_schemas", "get_connection_databases", "get_connection_tables", "get_connection_table_columns"},
|
||||
ToolNames: []string{"get_models", "get_explores", "get_dimensions", "get_measures", "get_filters", "get_parameters", "query", "query_sql", "query_url", "get_looks", "run_look", "make_look", "get_dashboards", "run_dashboard", "make_dashboard", "add_dashboard_element", "add_dashboard_filter", "generate_embed_url", "health_pulse", "health_analyze", "health_vacuum", "dev_mode", "get_projects", "get_project_files", "get_project_file", "create_project_file", "update_project_file", "delete_project_file", "validate_project", "get_connections", "get_connection_schemas", "get_connection_databases", "get_connection_tables", "get_connection_table_columns"},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -33,10 +33,10 @@ to expose your developer assistant tools to a Looker instance:
|
||||
## Set up Looker
|
||||
|
||||
1. Get a Looker Client ID and Client Secret. Follow the directions
|
||||
[here](https://cloud.google.com/looker/docs/api-auth#authentication_with_an_sdk).
|
||||
[here](https://cloud.google.com/looker/docs/api-auth#authentication_wit_an_sdk).
|
||||
|
||||
1. Have the base URL of your Looker instance available. It is likely
|
||||
something like `https://looker.example.com`. In some cases the API is
|
||||
something like `https://loer.example.com`. In some cases the API is
|
||||
listening at a different port, and you will need to use
|
||||
`https://looker.example.com:19999` instead.
|
||||
|
||||
|
||||
@@ -543,9 +543,6 @@ See [Usage Examples](../reference/cli.md#examples).
|
||||
* `create_project_file`: Create a new LookML file.
|
||||
* `update_project_file`: Update an existing LookML file.
|
||||
* `delete_project_file`: Delete a LookML file.
|
||||
* `get_project_directories`: Retrieves a list of project directories for a given LookML project.
|
||||
* `create_project_directory`: Creates a new directory within a specified LookML project.
|
||||
* `delete_project_directory`: Deletes a directory from a specified LookML project.
|
||||
* `validate_project`: Check the syntax of a LookML project.
|
||||
* `get_connections`: Get the available connections in a Looker instance.
|
||||
* `get_connection_schemas`: Get the available schemas in a connection.
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
---
|
||||
title: "looker-create-project-directory"
|
||||
type: docs
|
||||
weight: 1
|
||||
description: >
|
||||
A "looker-create-project-directory" tool creates a new directory in a LookML project.
|
||||
aliases:
|
||||
- /resources/tools/looker-create-project-directory
|
||||
---
|
||||
|
||||
## About
|
||||
|
||||
A `looker-create-project-directory` tool creates a new directory within a specified LookML project.
|
||||
|
||||
It's compatible with the following sources:
|
||||
|
||||
- [looker](../../sources/looker.md)
|
||||
|
||||
## Example
|
||||
|
||||
```yaml
|
||||
kind: tools
|
||||
name: looker-create-project-directory
|
||||
type: looker-create-project-directory
|
||||
source: looker-source
|
||||
description: |
|
||||
This tool creates a new directory within a specific LookML project.
|
||||
It is useful for organizing project files.
|
||||
|
||||
Parameters:
|
||||
- project_id (string): The ID of the LookML project.
|
||||
- directory_path (string): The path of the directory to create.
|
||||
|
||||
Output:
|
||||
A string confirming the creation of the directory.
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|-------------|:--------:|:------------:|----------------------------------------------------|
|
||||
| type | string | true | Must be "looker-create-project-directory". |
|
||||
| source | string | true | Name of the source Looker instance. |
|
||||
| description | string | true | Description of the tool that is passed to the LLM. |
|
||||
@@ -1,44 +0,0 @@
|
||||
---
|
||||
title: "looker-delete-project-directory"
|
||||
type: docs
|
||||
weight: 1
|
||||
description: >
|
||||
A "looker-delete-project-directory" tool deletes a directory from a LookML project.
|
||||
aliases:
|
||||
- /resources/tools/looker-delete-project-directory
|
||||
---
|
||||
|
||||
## About
|
||||
|
||||
A `looker-delete-project-directory` tool deletes a directory from a specified LookML project.
|
||||
|
||||
It's compatible with the following sources:
|
||||
|
||||
- [looker](../../sources/looker.md)
|
||||
|
||||
## Example
|
||||
|
||||
```yaml
|
||||
kind: tools
|
||||
name: looker-delete-project-directory
|
||||
type: looker-delete-project-directory
|
||||
source: looker-source
|
||||
description: |
|
||||
This tool deletes a directory from a specific LookML project.
|
||||
It is useful for removing unnecessary or obsolete directories.
|
||||
|
||||
Parameters:
|
||||
- project_id (string): The ID of the LookML project.
|
||||
- directory_path (string): The path of the directory to delete.
|
||||
|
||||
Output:
|
||||
A string confirming the deletion of the directory.
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|-------------|:--------:|:------------:|----------------------------------------------------|
|
||||
| type | string | true | Must be "looker-delete-project-directory". |
|
||||
| source | string | true | Name of the source Looker instance. |
|
||||
| description | string | true | Description of the tool that is passed to the LLM. |
|
||||
@@ -1,43 +0,0 @@
|
||||
---
|
||||
title: "looker-get-project-directories"
|
||||
type: docs
|
||||
weight: 1
|
||||
description: >
|
||||
A "looker-get-project-directories" tool returns the directories within a specific LookML project.
|
||||
aliases:
|
||||
- /resources/tools/looker-get-project-directories
|
||||
---
|
||||
|
||||
## About
|
||||
|
||||
A `looker-get-project-directories` tool retrieves the directories within a specified LookML project.
|
||||
|
||||
It's compatible with the following sources:
|
||||
|
||||
- [looker](../../sources/looker.md)
|
||||
|
||||
## Example
|
||||
|
||||
```yaml
|
||||
kind: tools
|
||||
name: looker-get-project-directories
|
||||
type: looker-get-project-directories
|
||||
source: looker-source
|
||||
description: |
|
||||
This tool retrieves a list of directories within a specific LookML project.
|
||||
It is useful for exploring the project structure.
|
||||
|
||||
Parameters:
|
||||
- project_id (string): The ID of the LookML project.
|
||||
|
||||
Output:
|
||||
A JSON array of strings, representing the directories within the project.
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|-------------|:--------:|:------------:|----------------------------------------------------|
|
||||
| type | string | true | Must be "looker-get-project-directories". |
|
||||
| source | string | true | Name of the source Looker instance. |
|
||||
| description | string | true | Description of the tool that is passed to the LLM. |
|
||||
2
go.mod
2
go.mod
@@ -29,7 +29,7 @@ require (
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
github.com/go-chi/chi/v5 v5.2.3
|
||||
github.com/go-chi/cors v1.2.2
|
||||
github.com/go-chi/httplog/v3 v3.3.0
|
||||
github.com/go-chi/httplog/v2 v2.1.1
|
||||
github.com/go-chi/render v1.0.3
|
||||
github.com/go-goquery/goquery v1.0.1
|
||||
github.com/go-playground/validator/v10 v10.28.0
|
||||
|
||||
4
go.sum
4
go.sum
@@ -902,8 +902,8 @@ github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
||||
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
|
||||
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-chi/httplog/v3 v3.3.0 h1:Gr6Y7nSzbpyCyRwKPOVKjDH3BH6TH5uvRNDsTZWDpvU=
|
||||
github.com/go-chi/httplog/v3 v3.3.0/go.mod h1:N/J1l5l1fozUrqIVuT8Z/HzNeSy8TF2EFyokPLe6y2w=
|
||||
github.com/go-chi/httplog/v2 v2.1.1 h1:ojojiu4PIaoeJ/qAO4GWUxJqvYUTobeo7zmuHQJAxRk=
|
||||
github.com/go-chi/httplog/v2 v2.1.1/go.mod h1:/XXdxicJsp4BA5fapgIC3VuTD+z0Z/VzukoB3VDc1YE=
|
||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||
github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw=
|
||||
|
||||
@@ -59,35 +59,25 @@ func NewStdLogger(outW, errW io.Writer, logLevel string) (Logger, error) {
|
||||
}
|
||||
|
||||
// DebugContext logs debug messages
|
||||
func (sl *StdLogger) DebugContext(ctx context.Context, msg string, keysAndValues ...any) {
|
||||
func (sl *StdLogger) DebugContext(ctx context.Context, msg string, keysAndValues ...interface{}) {
|
||||
sl.outLogger.DebugContext(ctx, msg, keysAndValues...)
|
||||
}
|
||||
|
||||
// InfoContext logs debug messages
|
||||
func (sl *StdLogger) InfoContext(ctx context.Context, msg string, keysAndValues ...any) {
|
||||
func (sl *StdLogger) InfoContext(ctx context.Context, msg string, keysAndValues ...interface{}) {
|
||||
sl.outLogger.InfoContext(ctx, msg, keysAndValues...)
|
||||
}
|
||||
|
||||
// WarnContext logs warning messages
|
||||
func (sl *StdLogger) WarnContext(ctx context.Context, msg string, keysAndValues ...any) {
|
||||
func (sl *StdLogger) WarnContext(ctx context.Context, msg string, keysAndValues ...interface{}) {
|
||||
sl.errLogger.WarnContext(ctx, msg, keysAndValues...)
|
||||
}
|
||||
|
||||
// ErrorContext logs error messages
|
||||
func (sl *StdLogger) ErrorContext(ctx context.Context, msg string, keysAndValues ...any) {
|
||||
func (sl *StdLogger) ErrorContext(ctx context.Context, msg string, keysAndValues ...interface{}) {
|
||||
sl.errLogger.ErrorContext(ctx, msg, keysAndValues...)
|
||||
}
|
||||
|
||||
// SlogLogger returns a single standard *slog.Logger that routes
|
||||
// records to the outLogger or errLogger based on the log level.
|
||||
func (sl *StdLogger) SlogLogger() *slog.Logger {
|
||||
splitHandler := &SplitHandler{
|
||||
OutHandler: sl.outLogger.Handler(),
|
||||
ErrHandler: sl.errLogger.Handler(),
|
||||
}
|
||||
return slog.New(splitHandler)
|
||||
}
|
||||
|
||||
const (
|
||||
Debug = "DEBUG"
|
||||
Info = "INFO"
|
||||
@@ -187,64 +177,21 @@ func NewStructuredLogger(outW, errW io.Writer, logLevel string) (Logger, error)
|
||||
}
|
||||
|
||||
// DebugContext logs debug messages
|
||||
func (sl *StructuredLogger) DebugContext(ctx context.Context, msg string, keysAndValues ...any) {
|
||||
func (sl *StructuredLogger) DebugContext(ctx context.Context, msg string, keysAndValues ...interface{}) {
|
||||
sl.outLogger.DebugContext(ctx, msg, keysAndValues...)
|
||||
}
|
||||
|
||||
// InfoContext logs info messages
|
||||
func (sl *StructuredLogger) InfoContext(ctx context.Context, msg string, keysAndValues ...any) {
|
||||
func (sl *StructuredLogger) InfoContext(ctx context.Context, msg string, keysAndValues ...interface{}) {
|
||||
sl.outLogger.InfoContext(ctx, msg, keysAndValues...)
|
||||
}
|
||||
|
||||
// WarnContext logs warning messages
|
||||
func (sl *StructuredLogger) WarnContext(ctx context.Context, msg string, keysAndValues ...any) {
|
||||
func (sl *StructuredLogger) WarnContext(ctx context.Context, msg string, keysAndValues ...interface{}) {
|
||||
sl.errLogger.WarnContext(ctx, msg, keysAndValues...)
|
||||
}
|
||||
|
||||
// ErrorContext logs error messages
|
||||
func (sl *StructuredLogger) ErrorContext(ctx context.Context, msg string, keysAndValues ...any) {
|
||||
func (sl *StructuredLogger) ErrorContext(ctx context.Context, msg string, keysAndValues ...interface{}) {
|
||||
sl.errLogger.ErrorContext(ctx, msg, keysAndValues...)
|
||||
}
|
||||
|
||||
// SlogLogger returns a single standard *slog.Logger that routes
|
||||
// records to the outLogger or errLogger based on the log level.
|
||||
func (sl *StructuredLogger) SlogLogger() *slog.Logger {
|
||||
splitHandler := &SplitHandler{
|
||||
OutHandler: sl.outLogger.Handler(),
|
||||
ErrHandler: sl.errLogger.Handler(),
|
||||
}
|
||||
return slog.New(splitHandler)
|
||||
}
|
||||
|
||||
type SplitHandler struct {
|
||||
OutHandler slog.Handler
|
||||
ErrHandler slog.Handler
|
||||
}
|
||||
|
||||
func (h *SplitHandler) Enabled(ctx context.Context, level slog.Level) bool {
|
||||
if level >= slog.LevelWarn {
|
||||
return h.ErrHandler.Enabled(ctx, level)
|
||||
}
|
||||
return h.OutHandler.Enabled(ctx, level)
|
||||
}
|
||||
|
||||
func (h *SplitHandler) Handle(ctx context.Context, r slog.Record) error {
|
||||
if r.Level >= slog.LevelWarn {
|
||||
return h.ErrHandler.Handle(ctx, r)
|
||||
}
|
||||
return h.OutHandler.Handle(ctx, r)
|
||||
}
|
||||
|
||||
func (h *SplitHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||
return &SplitHandler{
|
||||
OutHandler: h.OutHandler.WithAttrs(attrs),
|
||||
ErrHandler: h.ErrHandler.WithAttrs(attrs),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *SplitHandler) WithGroup(name string) slog.Handler {
|
||||
return &SplitHandler{
|
||||
OutHandler: h.OutHandler.WithGroup(name),
|
||||
ErrHandler: h.ErrHandler.WithGroup(name),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,20 +16,16 @@ package log
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
// Logger is the interface used throughout the project for logging.
|
||||
type Logger interface {
|
||||
// DebugContext is for reporting additional information about internal operations.
|
||||
DebugContext(ctx context.Context, format string, args ...any)
|
||||
DebugContext(ctx context.Context, format string, args ...interface{})
|
||||
// InfoContext is for reporting informational messages.
|
||||
InfoContext(ctx context.Context, format string, args ...any)
|
||||
InfoContext(ctx context.Context, format string, args ...interface{})
|
||||
// WarnContext is for reporting warning messages.
|
||||
WarnContext(ctx context.Context, format string, args ...any)
|
||||
WarnContext(ctx context.Context, format string, args ...interface{})
|
||||
// ErrorContext is for reporting errors.
|
||||
ErrorContext(ctx context.Context, format string, args ...any)
|
||||
// Single standard slog.Logger that routes records to the outLogger or
|
||||
// errLogger based on log levels
|
||||
SlogLogger() *slog.Logger
|
||||
ErrorContext(ctx context.Context, format string, args ...interface{})
|
||||
}
|
||||
|
||||
@@ -959,48 +959,6 @@ tools:
|
||||
Output:
|
||||
A confirmation message upon successful file deletion.
|
||||
|
||||
get_project_directories:
|
||||
kind: looker-get-project-directories
|
||||
source: looker-source
|
||||
description: |
|
||||
This tool retrieves the list of directories within a specified LookML project.
|
||||
|
||||
Parameters:
|
||||
- project_id (required): The unique ID of the LookML project.
|
||||
|
||||
Output:
|
||||
A JSON array of strings, where each string is the name of a directory within the project.
|
||||
|
||||
create_project_directory:
|
||||
kind: looker-create-project-directory
|
||||
source: looker-source
|
||||
description: |
|
||||
This tool creates a new directory within a specified LookML project.
|
||||
|
||||
Prerequisite: The Looker session must be in Development Mode. Use `dev_mode: true` first.
|
||||
|
||||
Parameters:
|
||||
- project_id (required): The unique ID of the LookML project.
|
||||
- directory_path (required): The path to the new directory within the project.
|
||||
|
||||
Output:
|
||||
A confirmation message upon successful directory creation.
|
||||
|
||||
delete_project_directory:
|
||||
kind: looker-delete-project-directory
|
||||
source: looker-source
|
||||
description: |
|
||||
This tool permanently deletes a specified directory within a LookML project.
|
||||
|
||||
Prerequisite: The Looker session must be in Development Mode. Use `dev_mode: true` first.
|
||||
|
||||
Parameters:
|
||||
- project_id (required): The unique ID of the LookML project.
|
||||
- directory_path (required): The path to the directory within the project.
|
||||
|
||||
Output:
|
||||
A confirmation message upon successful directory deletion.
|
||||
|
||||
validate_project:
|
||||
kind: looker-validate-project
|
||||
source: looker-source
|
||||
@@ -1129,9 +1087,6 @@ toolsets:
|
||||
- create_project_file
|
||||
- update_project_file
|
||||
- delete_project_file
|
||||
- get_project_directories
|
||||
- create_project_directory
|
||||
- delete_project_directory
|
||||
- validate_project
|
||||
- get_connections
|
||||
- get_connection_schemas
|
||||
|
||||
@@ -28,7 +28,7 @@ import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/cors"
|
||||
"github.com/go-chi/httplog/v3"
|
||||
"github.com/go-chi/httplog/v2"
|
||||
"github.com/googleapis/genai-toolbox/internal/auth"
|
||||
"github.com/googleapis/genai-toolbox/internal/embeddingmodels"
|
||||
"github.com/googleapis/genai-toolbox/internal/log"
|
||||
@@ -347,16 +347,31 @@ func NewServer(ctx context.Context, cfg ServerConfig) (*Server, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to initialize http log: %w", err)
|
||||
}
|
||||
|
||||
schema := *httplog.SchemaGCP
|
||||
schema.Level = cfg.LogLevel.String()
|
||||
schema.Concise(true)
|
||||
httpOpts := &httplog.Options{
|
||||
Level: logLevel,
|
||||
Schema: &schema,
|
||||
var httpOpts httplog.Options
|
||||
switch cfg.LoggingFormat.String() {
|
||||
case "json":
|
||||
httpOpts = httplog.Options{
|
||||
JSON: true,
|
||||
LogLevel: logLevel,
|
||||
Concise: true,
|
||||
RequestHeaders: false,
|
||||
MessageFieldName: "message",
|
||||
SourceFieldName: "logging.googleapis.com/sourceLocation",
|
||||
TimeFieldName: "timestamp",
|
||||
LevelFieldName: "severity",
|
||||
}
|
||||
case "standard":
|
||||
httpOpts = httplog.Options{
|
||||
LogLevel: logLevel,
|
||||
Concise: true,
|
||||
RequestHeaders: false,
|
||||
MessageFieldName: "message",
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid Logging format: %q", cfg.LoggingFormat.String())
|
||||
}
|
||||
logger := l.SlogLogger()
|
||||
r.Use(httplog.RequestLogger(logger, httpOpts))
|
||||
httpLogger := httplog.NewLogger("httplog", httpOpts)
|
||||
r.Use(httplog.RequestLogger(httpLogger))
|
||||
|
||||
sourcesMap, authServicesMap, embeddingModelsMap, toolsMap, toolsetsMap, promptsMap, promptsetsMap, err := InitializeConfigs(ctx, cfg)
|
||||
if err != nil {
|
||||
|
||||
@@ -107,15 +107,15 @@ func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error)
|
||||
return nil, fmt.Errorf("expected allowedDataset to have at least 2 parts (project.dataset): %s", datasetFQN)
|
||||
}
|
||||
datasetID := parts[1]
|
||||
fmt.Fprintf(&sqlDescriptionBuilder, " The query must only access the `%s` dataset. "+
|
||||
sqlDescriptionBuilder.WriteString(fmt.Sprintf(" The query must only access the `%s` dataset. "+
|
||||
"To query a table within this dataset (e.g., `my_table`), "+
|
||||
"qualify it with the dataset id (e.g., `%s.my_table`).", datasetFQN, datasetID)
|
||||
"qualify it with the dataset id (e.g., `%s.my_table`).", datasetFQN, datasetID))
|
||||
} else {
|
||||
datasetIDs := []string{}
|
||||
for _, ds := range allowedDatasets {
|
||||
datasetIDs = append(datasetIDs, fmt.Sprintf("`%s`", ds))
|
||||
}
|
||||
fmt.Fprintf(&sqlDescriptionBuilder, " The query must only access datasets from the following list: %s.", strings.Join(datasetIDs, ", "))
|
||||
sqlDescriptionBuilder.WriteString(fmt.Sprintf(" The query must only access datasets from the following list: %s.", strings.Join(datasetIDs, ", ")))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2026 Google LLC
|
||||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -301,32 +301,3 @@ func UpdateProjectFile(l *v4.LookerSDK, projectId string, fileContent FileConten
|
||||
err := l.AuthSession.Do(nil, "PUT", "/4.0", path, nil, fileContent, options)
|
||||
return err
|
||||
}
|
||||
|
||||
func GetProjectDirectories(l *v4.LookerSDK, projectId string, options *rtl.ApiSettings) (string, error) {
|
||||
var result string
|
||||
path := fmt.Sprintf("/projects/%s/directories", url.PathEscape(projectId))
|
||||
err := l.AuthSession.Do(&result, "GET", "/4.0", path, nil, nil, options)
|
||||
return result, err
|
||||
}
|
||||
|
||||
type Directory struct {
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
func CreateProjectDirectory(l *v4.LookerSDK, projectId string, directoryPath string, options *rtl.ApiSettings) error {
|
||||
d := Directory{
|
||||
Path: directoryPath,
|
||||
}
|
||||
var result string
|
||||
path := fmt.Sprintf("/projects/%s/directories", url.PathEscape(projectId))
|
||||
return l.AuthSession.Do(&result, "POST", "/4.0", path, nil, d, options)
|
||||
}
|
||||
|
||||
func DeleteProjectDirectory(l *v4.LookerSDK, projectId string, directoryPath string, options *rtl.ApiSettings) error {
|
||||
var query = map[string]any{
|
||||
"path": directoryPath,
|
||||
}
|
||||
var result string
|
||||
path := fmt.Sprintf("/projects/%s/directories", url.PathEscape(projectId))
|
||||
return l.AuthSession.Do(&result, "DELETE", "/4.0", path, query, nil, options)
|
||||
}
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
// 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 lookercreateprojectdirectory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
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/tools/looker/lookercommon"
|
||||
"github.com/googleapis/genai-toolbox/internal/util"
|
||||
"github.com/googleapis/genai-toolbox/internal/util/parameters"
|
||||
|
||||
"github.com/looker-open-source/sdk-codegen/go/rtl"
|
||||
v4 "github.com/looker-open-source/sdk-codegen/go/sdk/v4"
|
||||
)
|
||||
|
||||
const resourceType string = "looker-create-project-directory"
|
||||
|
||||
func init() {
|
||||
if !tools.Register(resourceType, newConfig) {
|
||||
panic(fmt.Sprintf("tool type %q already registered", resourceType))
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
UseClientAuthorization() bool
|
||||
GetAuthTokenHeaderName() string
|
||||
LookerApiSettings() *rtl.ApiSettings
|
||||
GetLookerSDK(string) (*v4.LookerSDK, error)
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Type string `yaml:"type" validate:"required"`
|
||||
Source string `yaml:"source" validate:"required"`
|
||||
Description string `yaml:"description" validate:"required"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
Annotations *tools.ToolAnnotations `yaml:"annotations,omitempty"`
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.ToolConfig = Config{}
|
||||
|
||||
func (cfg Config) ToolConfigType() string {
|
||||
return resourceType
|
||||
}
|
||||
|
||||
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
|
||||
projectIdParameter := parameters.NewStringParameter("project_id", "The id of the project")
|
||||
directoryPathParameter := parameters.NewStringParameter("directory_path", "The path to create in the project")
|
||||
params := parameters.Parameters{projectIdParameter, directoryPathParameter}
|
||||
|
||||
annotations := cfg.Annotations
|
||||
if annotations == nil {
|
||||
readOnlyHint := false
|
||||
annotations = &tools.ToolAnnotations{
|
||||
ReadOnlyHint: &readOnlyHint,
|
||||
}
|
||||
}
|
||||
|
||||
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, params, annotations)
|
||||
|
||||
// finish tool setup
|
||||
return Tool{
|
||||
Config: cfg,
|
||||
Parameters: params,
|
||||
manifest: tools.Manifest{
|
||||
Description: cfg.Description,
|
||||
Parameters: params.Manifest(),
|
||||
AuthRequired: cfg.AuthRequired,
|
||||
},
|
||||
mcpManifest: mcpManifest,
|
||||
}, 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) ToConfig() tools.ToolConfig {
|
||||
return t.Config
|
||||
}
|
||||
|
||||
func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, params parameters.ParamValues, accessToken tools.AccessToken) (any, util.ToolboxError) {
|
||||
source, err := tools.GetCompatibleSource[compatibleSource](resourceMgr, t.Source, t.Name, t.Type)
|
||||
if err != nil {
|
||||
return nil, util.NewClientServerError("source used is not compatible with the tool", http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
sdk, err := source.GetLookerSDK(string(accessToken))
|
||||
if err != nil {
|
||||
return nil, util.NewClientServerError("error getting sdk", http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
mapParams := params.AsMap()
|
||||
projectId, ok := mapParams["project_id"].(string)
|
||||
if !ok {
|
||||
return nil, util.NewAgentError(fmt.Sprintf("'project_id' must be a string, got %T", mapParams["project_id"]), nil)
|
||||
}
|
||||
directoryPath, ok := mapParams["directory_path"].(string)
|
||||
if !ok {
|
||||
return nil, util.NewAgentError(fmt.Sprintf("'directory_path' must be a string, got %T", mapParams["directory_path"]), nil)
|
||||
}
|
||||
|
||||
err = lookercommon.CreateProjectDirectory(sdk, projectId, directoryPath, source.LookerApiSettings())
|
||||
if err != nil {
|
||||
return nil, util.ProcessGeneralError(err)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("Created directory %s in project %s", directoryPath, projectId), nil
|
||||
}
|
||||
|
||||
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) RequiresClientAuthorization(resourceMgr tools.SourceProvider) (bool, error) {
|
||||
source, err := tools.GetCompatibleSource[compatibleSource](resourceMgr, t.Source, t.Name, t.Type)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return source.UseClientAuthorization(), nil
|
||||
}
|
||||
|
||||
func (t Tool) Authorized(verifiedAuthServices []string) bool {
|
||||
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
|
||||
}
|
||||
|
||||
func (t Tool) GetAuthTokenHeaderName(resourceMgr tools.SourceProvider) (string, error) {
|
||||
source, err := tools.GetCompatibleSource[compatibleSource](resourceMgr, t.Source, t.Name, t.Type)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return source.GetAuthTokenHeaderName(), nil
|
||||
}
|
||||
|
||||
func (t Tool) GetParameters() parameters.Parameters {
|
||||
return t.Parameters
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
// 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 lookercreateprojectdirectory_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/googleapis/genai-toolbox/internal/server"
|
||||
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||
lkr "github.com/googleapis/genai-toolbox/internal/tools/looker/lookercreateprojectdirectory"
|
||||
)
|
||||
|
||||
func TestParseFromYamlLookerCreateProjectDirectory(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: `
|
||||
kind: tools
|
||||
name: example_tool
|
||||
type: looker-create-project-directory
|
||||
source: my-instance
|
||||
description: some description
|
||||
`,
|
||||
want: server.ToolConfigs{
|
||||
"example_tool": lkr.Config{
|
||||
Name: "example_tool",
|
||||
Type: "looker-create-project-directory",
|
||||
Source: "my-instance",
|
||||
Description: "some description",
|
||||
AuthRequired: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
// Parse contents
|
||||
_, _, _, got, _, _, err := server.UnmarshalResourceConfig(ctx, testutils.FormatYaml(tc.in))
|
||||
if err != nil {
|
||||
t.Fatalf("unable to unmarshal: %s", err)
|
||||
}
|
||||
if diff := cmp.Diff(tc.want, got); diff != "" {
|
||||
t.Fatalf("incorrect parse: diff %v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestFailParseFromYamlLookerCreateProjectDirectory(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: "Invalid method",
|
||||
in: `
|
||||
kind: tools
|
||||
name: example_tool
|
||||
type: looker-create-project-directory
|
||||
source: my-instance
|
||||
method: GOT
|
||||
description: some description
|
||||
`,
|
||||
err: "error unmarshaling tools: unable to parse tool \"example_tool\" as type \"looker-create-project-directory\": [3:1] unknown field \"method\"\n 1 | authRequired: []\n 2 | description: some description\n> 3 | method: GOT\n ^\n 4 | name: example_tool\n 5 | source: my-instance\n 6 | type: looker-create-project-directory",
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
_, _, _, _, _, _, err := server.UnmarshalResourceConfig(ctx, testutils.FormatYaml(tc.in))
|
||||
if err == nil {
|
||||
t.Fatalf("expect parsing to fail")
|
||||
}
|
||||
errStr := err.Error()
|
||||
if !strings.Contains(errStr, tc.err) {
|
||||
t.Fatalf("unexpected error string: got %q, want substring %q", errStr, tc.err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
// 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 lookerdeleteprojectdirectory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
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/tools/looker/lookercommon"
|
||||
"github.com/googleapis/genai-toolbox/internal/util"
|
||||
"github.com/googleapis/genai-toolbox/internal/util/parameters"
|
||||
|
||||
"github.com/looker-open-source/sdk-codegen/go/rtl"
|
||||
v4 "github.com/looker-open-source/sdk-codegen/go/sdk/v4"
|
||||
)
|
||||
|
||||
const resourceType string = "looker-delete-project-directory"
|
||||
|
||||
func init() {
|
||||
if !tools.Register(resourceType, newConfig) {
|
||||
panic(fmt.Sprintf("tool type %q already registered", resourceType))
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
UseClientAuthorization() bool
|
||||
GetAuthTokenHeaderName() string
|
||||
LookerApiSettings() *rtl.ApiSettings
|
||||
GetLookerSDK(string) (*v4.LookerSDK, error)
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Type string `yaml:"type" validate:"required"`
|
||||
Source string `yaml:"source" validate:"required"`
|
||||
Description string `yaml:"description" validate:"required"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
Annotations *tools.ToolAnnotations `yaml:"annotations,omitempty"`
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.ToolConfig = Config{}
|
||||
|
||||
func (cfg Config) ToolConfigType() string {
|
||||
return resourceType
|
||||
}
|
||||
|
||||
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
|
||||
projectIdParameter := parameters.NewStringParameter("project_id", "The id of the project")
|
||||
directoryPathParameter := parameters.NewStringParameter("directory_path", "The path to delete in the project")
|
||||
params := parameters.Parameters{projectIdParameter, directoryPathParameter}
|
||||
|
||||
annotations := cfg.Annotations
|
||||
if annotations == nil {
|
||||
readOnlyHint := false
|
||||
destructiveHint := true
|
||||
annotations = &tools.ToolAnnotations{
|
||||
ReadOnlyHint: &readOnlyHint,
|
||||
DestructiveHint: &destructiveHint,
|
||||
}
|
||||
}
|
||||
|
||||
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, params, annotations)
|
||||
|
||||
// finish tool setup
|
||||
return Tool{
|
||||
Config: cfg,
|
||||
Parameters: params,
|
||||
manifest: tools.Manifest{
|
||||
Description: cfg.Description,
|
||||
Parameters: params.Manifest(),
|
||||
AuthRequired: cfg.AuthRequired,
|
||||
},
|
||||
mcpManifest: mcpManifest,
|
||||
}, 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) ToConfig() tools.ToolConfig {
|
||||
return t.Config
|
||||
}
|
||||
|
||||
func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, params parameters.ParamValues, accessToken tools.AccessToken) (any, util.ToolboxError) {
|
||||
source, err := tools.GetCompatibleSource[compatibleSource](resourceMgr, t.Source, t.Name, t.Type)
|
||||
if err != nil {
|
||||
return nil, util.NewClientServerError("source used is not compatible with the tool", http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
sdk, err := source.GetLookerSDK(string(accessToken))
|
||||
if err != nil {
|
||||
return nil, util.NewClientServerError("error getting sdk", http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
mapParams := params.AsMap()
|
||||
projectId, ok := mapParams["project_id"].(string)
|
||||
if !ok {
|
||||
return nil, util.NewAgentError(fmt.Sprintf("'project_id' must be a string, got %T", mapParams["project_id"]), nil)
|
||||
}
|
||||
directoryPath, ok := mapParams["directory_path"].(string)
|
||||
if !ok {
|
||||
return nil, util.NewAgentError(fmt.Sprintf("'directory_path' must be a string, got %T", mapParams["directory_path"]), nil)
|
||||
}
|
||||
|
||||
err = lookercommon.DeleteProjectDirectory(sdk, projectId, directoryPath, source.LookerApiSettings())
|
||||
if err != nil {
|
||||
return nil, util.ProcessGeneralError(err)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("Deleted directory %s in project %s", directoryPath, projectId), nil
|
||||
}
|
||||
|
||||
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) RequiresClientAuthorization(resourceMgr tools.SourceProvider) (bool, error) {
|
||||
source, err := tools.GetCompatibleSource[compatibleSource](resourceMgr, t.Source, t.Name, t.Type)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return source.UseClientAuthorization(), nil
|
||||
}
|
||||
|
||||
func (t Tool) Authorized(verifiedAuthServices []string) bool {
|
||||
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
|
||||
}
|
||||
|
||||
func (t Tool) GetAuthTokenHeaderName(resourceMgr tools.SourceProvider) (string, error) {
|
||||
source, err := tools.GetCompatibleSource[compatibleSource](resourceMgr, t.Source, t.Name, t.Type)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return source.GetAuthTokenHeaderName(), nil
|
||||
}
|
||||
|
||||
func (t Tool) GetParameters() parameters.Parameters {
|
||||
return t.Parameters
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
// 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 lookerdeleteprojectdirectory_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/googleapis/genai-toolbox/internal/server"
|
||||
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||
lkr "github.com/googleapis/genai-toolbox/internal/tools/looker/lookerdeleteprojectdirectory"
|
||||
)
|
||||
|
||||
func TestParseFromYamlLookerDeleteProjectDirectory(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: `
|
||||
kind: tools
|
||||
name: example_tool
|
||||
type: looker-delete-project-directory
|
||||
source: my-instance
|
||||
description: some description
|
||||
`,
|
||||
want: server.ToolConfigs{
|
||||
"example_tool": lkr.Config{
|
||||
Name: "example_tool",
|
||||
Type: "looker-delete-project-directory",
|
||||
Source: "my-instance",
|
||||
Description: "some description",
|
||||
AuthRequired: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
// Parse contents
|
||||
_, _, _, got, _, _, err := server.UnmarshalResourceConfig(ctx, testutils.FormatYaml(tc.in))
|
||||
if err != nil {
|
||||
t.Fatalf("unable to unmarshal: %s", err)
|
||||
}
|
||||
if diff := cmp.Diff(tc.want, got); diff != "" {
|
||||
t.Fatalf("incorrect parse: diff %v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestFailParseFromYamlLookerDeleteProjectDirectory(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: "Invalid method",
|
||||
in: `
|
||||
kind: tools
|
||||
name: example_tool
|
||||
type: looker-delete-project-directory
|
||||
source: my-instance
|
||||
method: GOT
|
||||
description: some description
|
||||
`,
|
||||
err: "error unmarshaling tools: unable to parse tool \"example_tool\" as type \"looker-delete-project-directory\": [3:1] unknown field \"method\"\n 1 | authRequired: []\n 2 | description: some description\n> 3 | method: GOT\n ^\n 4 | name: example_tool\n 5 | source: my-instance\n 6 | type: looker-delete-project-directory",
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
_, _, _, _, _, _, err := server.UnmarshalResourceConfig(ctx, testutils.FormatYaml(tc.in))
|
||||
if err == nil {
|
||||
t.Fatalf("expect parsing to fail")
|
||||
}
|
||||
errStr := err.Error()
|
||||
if !strings.Contains(errStr, tc.err) {
|
||||
t.Fatalf("unexpected error string: got %q, want substring %q", errStr, tc.err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
// 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 lookergetprojectdirectories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
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/tools/looker/lookercommon"
|
||||
"github.com/googleapis/genai-toolbox/internal/util"
|
||||
"github.com/googleapis/genai-toolbox/internal/util/parameters"
|
||||
|
||||
"github.com/looker-open-source/sdk-codegen/go/rtl"
|
||||
v4 "github.com/looker-open-source/sdk-codegen/go/sdk/v4"
|
||||
)
|
||||
|
||||
const resourceType string = "looker-get-project-directories"
|
||||
|
||||
func init() {
|
||||
if !tools.Register(resourceType, newConfig) {
|
||||
panic(fmt.Sprintf("tool type %q already registered", resourceType))
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
UseClientAuthorization() bool
|
||||
GetAuthTokenHeaderName() string
|
||||
LookerApiSettings() *rtl.ApiSettings
|
||||
GetLookerSDK(string) (*v4.LookerSDK, error)
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Type string `yaml:"type" validate:"required"`
|
||||
Source string `yaml:"source" validate:"required"`
|
||||
Description string `yaml:"description" validate:"required"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
Annotations *tools.ToolAnnotations `yaml:"annotations,omitempty"`
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.ToolConfig = Config{}
|
||||
|
||||
func (cfg Config) ToolConfigType() string {
|
||||
return resourceType
|
||||
}
|
||||
|
||||
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
|
||||
projectIdParameter := parameters.NewStringParameter("project_id", "The id of the project")
|
||||
params := parameters.Parameters{projectIdParameter}
|
||||
|
||||
annotations := cfg.Annotations
|
||||
if annotations == nil {
|
||||
readOnlyHint := true
|
||||
annotations = &tools.ToolAnnotations{
|
||||
ReadOnlyHint: &readOnlyHint,
|
||||
}
|
||||
}
|
||||
|
||||
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, params, annotations)
|
||||
|
||||
// finish tool setup
|
||||
return Tool{
|
||||
Config: cfg,
|
||||
Parameters: params,
|
||||
manifest: tools.Manifest{
|
||||
Description: cfg.Description,
|
||||
Parameters: params.Manifest(),
|
||||
AuthRequired: cfg.AuthRequired,
|
||||
},
|
||||
mcpManifest: mcpManifest,
|
||||
}, 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) ToConfig() tools.ToolConfig {
|
||||
return t.Config
|
||||
}
|
||||
|
||||
func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, params parameters.ParamValues, accessToken tools.AccessToken) (any, util.ToolboxError) {
|
||||
source, err := tools.GetCompatibleSource[compatibleSource](resourceMgr, t.Source, t.Name, t.Type)
|
||||
if err != nil {
|
||||
return nil, util.NewClientServerError("source used is not compatible with the tool", http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
sdk, err := source.GetLookerSDK(string(accessToken))
|
||||
if err != nil {
|
||||
return nil, util.NewClientServerError("error getting sdk", http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
mapParams := params.AsMap()
|
||||
projectId, ok := mapParams["project_id"].(string)
|
||||
if !ok {
|
||||
return nil, util.NewAgentError(fmt.Sprintf("'project_id' must be a string, got %T", mapParams["project_id"]), nil)
|
||||
}
|
||||
|
||||
resp, err := lookercommon.GetProjectDirectories(sdk, projectId, source.LookerApiSettings())
|
||||
if err != nil {
|
||||
return nil, util.ProcessGeneralError(err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
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) RequiresClientAuthorization(resourceMgr tools.SourceProvider) (bool, error) {
|
||||
source, err := tools.GetCompatibleSource[compatibleSource](resourceMgr, t.Source, t.Name, t.Type)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return source.UseClientAuthorization(), nil
|
||||
}
|
||||
|
||||
func (t Tool) Authorized(verifiedAuthServices []string) bool {
|
||||
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
|
||||
}
|
||||
|
||||
func (t Tool) GetAuthTokenHeaderName(resourceMgr tools.SourceProvider) (string, error) {
|
||||
source, err := tools.GetCompatibleSource[compatibleSource](resourceMgr, t.Source, t.Name, t.Type)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return source.GetAuthTokenHeaderName(), nil
|
||||
}
|
||||
|
||||
func (t Tool) GetParameters() parameters.Parameters {
|
||||
return t.Parameters
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
// 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 lookergetprojectdirectories_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/googleapis/genai-toolbox/internal/server"
|
||||
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||
lkr "github.com/googleapis/genai-toolbox/internal/tools/looker/lookergetprojectdirectories"
|
||||
)
|
||||
|
||||
func TestParseFromYamlLookerGetProjectDirectories(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: `
|
||||
kind: tools
|
||||
name: example_tool
|
||||
type: looker-get-project-directories
|
||||
source: my-instance
|
||||
description: some description
|
||||
`,
|
||||
want: server.ToolConfigs{
|
||||
"example_tool": lkr.Config{
|
||||
Name: "example_tool",
|
||||
Type: "looker-get-project-directories",
|
||||
Source: "my-instance",
|
||||
Description: "some description",
|
||||
AuthRequired: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
// Parse contents
|
||||
_, _, _, got, _, _, err := server.UnmarshalResourceConfig(ctx, testutils.FormatYaml(tc.in))
|
||||
if err != nil {
|
||||
t.Fatalf("unable to unmarshal: %s", err)
|
||||
}
|
||||
if diff := cmp.Diff(tc.want, got); diff != "" {
|
||||
t.Fatalf("incorrect parse: diff %v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestFailParseFromYamlLookerGetProjectDirectories(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: "Invalid method",
|
||||
in: `
|
||||
kind: tools
|
||||
name: example_tool
|
||||
type: looker-get-project-directories
|
||||
source: my-instance
|
||||
method: GOT
|
||||
description: some description
|
||||
`,
|
||||
err: "error unmarshaling tools: unable to parse tool \"example_tool\" as type \"looker-get-project-directories\": [3:1] unknown field \"method\"\n 1 | authRequired: []\n 2 | description: some description\n> 3 | method: GOT\n ^\n 4 | name: example_tool\n 5 | source: my-instance\n 6 | type: looker-get-project-directories",
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
_, _, _, _, _, _, err := server.UnmarshalResourceConfig(ctx, testutils.FormatYaml(tc.in))
|
||||
if err == nil {
|
||||
t.Fatalf("expect parsing to fail")
|
||||
}
|
||||
errStr := err.Error()
|
||||
if !strings.Contains(errStr, tc.err) {
|
||||
t.Fatalf("unexpected error string: got %q, want substring %q", errStr, tc.err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -129,18 +129,13 @@ func TestAlloyDBPgToolEndpoints(t *testing.T) {
|
||||
t.Fatalf("unable to create AlloyDB connection pool: %s", err)
|
||||
}
|
||||
|
||||
// Generate a unique ID
|
||||
uniqueID := strings.ReplaceAll(uuid.New().String(), "-", "")
|
||||
// cleanup test environment
|
||||
tests.CleanupPostgresTables(t, ctx, pool)
|
||||
|
||||
// This will execute after all tool tests complete (success, fail, or t.Fatal)
|
||||
t.Cleanup(func() {
|
||||
tests.CleanupPostgresTables(t, context.Background(), pool, uniqueID)
|
||||
})
|
||||
|
||||
//Create table names using the UUID
|
||||
tableNameParam := "param_table_" + uniqueID
|
||||
tableNameAuth := "auth_table_" + uniqueID
|
||||
tableNameTemplateParam := "template_param_table_" + uniqueID
|
||||
// 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 := tests.GetPostgresSQLParamToolInfo(tableNameParam)
|
||||
|
||||
@@ -114,18 +114,13 @@ func TestCloudSQLPgSimpleToolEndpoints(t *testing.T) {
|
||||
t.Fatalf("unable to create Cloud SQL connection pool: %s", err)
|
||||
}
|
||||
|
||||
// Generate a unique ID
|
||||
uniqueID := strings.ReplaceAll(uuid.New().String(), "-", "")
|
||||
// cleanup test environment
|
||||
tests.CleanupPostgresTables(t, ctx, pool)
|
||||
|
||||
// This will execute after all tool tests complete (success, fail, or t.Fatal)
|
||||
t.Cleanup(func() {
|
||||
tests.CleanupPostgresTables(t, context.Background(), pool, uniqueID)
|
||||
})
|
||||
|
||||
//Create table names using the UUID
|
||||
tableNameParam := "param_table_" + uniqueID
|
||||
tableNameAuth := "auth_table_" + uniqueID
|
||||
tableNameTemplateParam := "template_param_table_" + uniqueID
|
||||
// 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 := tests.GetPostgresSQLParamToolInfo(tableNameParam)
|
||||
|
||||
@@ -109,18 +109,13 @@ func TestCockroachDB(t *testing.T) {
|
||||
}
|
||||
t.Logf("✅ Connected to: %s", version)
|
||||
|
||||
// Generate a unique ID
|
||||
uniqueID := strings.ReplaceAll(uuid.New().String(), "-", "")
|
||||
// cleanup test environment
|
||||
tests.CleanupPostgresTables(t, ctx, pool)
|
||||
|
||||
// This will execute after all tool tests complete (success, fail, or t.Fatal)
|
||||
t.Cleanup(func() {
|
||||
tests.CleanupPostgresTables(t, context.Background(), pool, uniqueID)
|
||||
})
|
||||
|
||||
//Create table names using the UUID
|
||||
tableNameParam := "param_table_" + uniqueID
|
||||
tableNameAuth := "auth_table_" + uniqueID
|
||||
tableNameTemplateParam := "template_param_table_" + uniqueID
|
||||
// create table names with UUID suffix
|
||||
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 (using CockroachDB explicit INT primary keys)
|
||||
createParamTableStmt, insertParamTableStmt, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, paramTestParams := tests.GetCockroachDBParamToolInfo(tableNameParam)
|
||||
|
||||
@@ -972,15 +972,14 @@ func TestCloudSQLMySQL_IPTypeParsingFromYAML(t *testing.T) {
|
||||
}
|
||||
|
||||
// Finds and drops all tables in a postgres database.
|
||||
func CleanupPostgresTables(t *testing.T, ctx context.Context, pool *pgxpool.Pool, uniqueID string) {
|
||||
|
||||
func CleanupPostgresTables(t *testing.T, ctx context.Context, pool *pgxpool.Pool) {
|
||||
query := `
|
||||
SELECT table_name FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name LIKE $1;`
|
||||
rows, err := pool.Query(ctx, query, "%\\_"+uniqueID)
|
||||
SELECT table_name FROM information_schema.tables
|
||||
WHERE table_schema = 'public' AND table_type = 'BASE TABLE';`
|
||||
|
||||
rows, err := pool.Query(ctx, query)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to query for tables for uniqueID %s in 'public' schema: %v", uniqueID, err)
|
||||
t.Fatalf("Failed to query for all tables in 'public' schema: %v", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
@@ -995,18 +994,13 @@ func CleanupPostgresTables(t *testing.T, ctx context.Context, pool *pgxpool.Pool
|
||||
}
|
||||
|
||||
if len(tablesToDrop) == 0 {
|
||||
t.Logf("INTEGRATION CLEANUP: No tables found for uniqueID: %s", uniqueID)
|
||||
return
|
||||
}
|
||||
|
||||
t.Logf("INTEGRATION CLEANUP: Dropping %d isolated tables: %v", len(tablesToDrop), tablesToDrop)
|
||||
|
||||
dropQuery := fmt.Sprintf("DROP TABLE IF EXISTS %s CASCADE;", strings.Join(tablesToDrop, ", "))
|
||||
|
||||
if _, err := pool.Exec(ctx, dropQuery); err != nil {
|
||||
t.Fatalf("Failed to drop tables for uniqueID %s in 'public' schema: %v", uniqueID, err)
|
||||
} else {
|
||||
t.Logf("INTEGRATION CLEANUP SUCCESS: Wiped all tables for uniqueID: %s", uniqueID)
|
||||
t.Fatalf("Failed to drop all tables in 'public' schema: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -222,21 +222,6 @@ func TestLooker(t *testing.T) {
|
||||
"source": "my-instance",
|
||||
"description": "Simple tool to test end to end functionality.",
|
||||
},
|
||||
"get_project_directories": map[string]any{
|
||||
"type": "looker-get-project-directories",
|
||||
"source": "my-instance",
|
||||
"description": "Simple tool to test end to end functionality.",
|
||||
},
|
||||
"create_project_directory": map[string]any{
|
||||
"type": "looker-create-project-directory",
|
||||
"source": "my-instance",
|
||||
"description": "Simple tool to test end to end functionality.",
|
||||
},
|
||||
"delete_project_directory": map[string]any{
|
||||
"type": "looker-delete-project-directory",
|
||||
"source": "my-instance",
|
||||
"description": "Simple tool to test end to end functionality.",
|
||||
},
|
||||
"validate_project": map[string]any{
|
||||
"type": "looker-validate-project",
|
||||
"source": "my-instance",
|
||||
@@ -1466,71 +1451,6 @@ func TestLooker(t *testing.T) {
|
||||
},
|
||||
},
|
||||
)
|
||||
tests.RunToolGetTestByName(t, "get_project_directories",
|
||||
map[string]any{
|
||||
"get_project_directories": map[string]any{
|
||||
"description": "Simple tool to test end to end functionality.",
|
||||
"authRequired": []any{},
|
||||
"parameters": []any{
|
||||
map[string]any{
|
||||
"authSources": []any{},
|
||||
"description": "The id of the project",
|
||||
"name": "project_id",
|
||||
"required": true,
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
tests.RunToolGetTestByName(t, "create_project_directory",
|
||||
map[string]any{
|
||||
"create_project_directory": map[string]any{
|
||||
"description": "Simple tool to test end to end functionality.",
|
||||
"authRequired": []any{},
|
||||
"parameters": []any{
|
||||
map[string]any{
|
||||
"authSources": []any{},
|
||||
"description": "The id of the project",
|
||||
"name": "project_id",
|
||||
"required": true,
|
||||
"type": "string",
|
||||
},
|
||||
map[string]any{
|
||||
"authSources": []any{},
|
||||
"description": "The path to create in the project",
|
||||
"name": "directory_path",
|
||||
"required": true,
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
tests.RunToolGetTestByName(t, "delete_project_directory",
|
||||
map[string]any{
|
||||
"delete_project_directory": map[string]any{
|
||||
"description": "Simple tool to test end to end functionality.",
|
||||
"authRequired": []any{},
|
||||
"parameters": []any{
|
||||
map[string]any{
|
||||
"authSources": []any{},
|
||||
"description": "The id of the project",
|
||||
"name": "project_id",
|
||||
"required": true,
|
||||
"type": "string",
|
||||
},
|
||||
map[string]any{
|
||||
"authSources": []any{},
|
||||
"description": "The path to delete in the project",
|
||||
"name": "directory_path",
|
||||
"required": true,
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
tests.RunToolGetTestByName(t, "validate_project",
|
||||
map[string]any{
|
||||
"validate_project": map[string]any{
|
||||
@@ -1767,15 +1687,6 @@ func TestLooker(t *testing.T) {
|
||||
wantResult = "deleted"
|
||||
tests.RunToolInvokeParametersTest(t, "delete_project_file", []byte(`{"project_id": "the_look", "file_path": "foo.view.lkml"}`), wantResult)
|
||||
|
||||
wantResult = "Created"
|
||||
tests.RunToolInvokeParametersTest(t, "create_project_directory", []byte(`{"project_id": "the_look", "directory_path": "foo_dir"}`), wantResult)
|
||||
|
||||
wantResult = "foo_dir"
|
||||
tests.RunToolInvokeParametersTest(t, "get_project_directories", []byte(`{"project_id": "the_look"}`), wantResult)
|
||||
|
||||
wantResult = "Deleted"
|
||||
tests.RunToolInvokeParametersTest(t, "delete_project_directory", []byte(`{"project_id": "the_look", "directory_path": "foo_dir"}`), wantResult)
|
||||
|
||||
wantResult = "\"errors\":[]"
|
||||
tests.RunToolInvokeParametersTest(t, "validate_project", []byte(`{"project_id": "the_look"}`), wantResult)
|
||||
|
||||
|
||||
@@ -93,18 +93,13 @@ func TestPostgres(t *testing.T) {
|
||||
t.Fatalf("unable to create postgres connection pool: %s", err)
|
||||
}
|
||||
|
||||
// Generate a unique ID
|
||||
uniqueID := strings.ReplaceAll(uuid.New().String(), "-", "")
|
||||
// cleanup test environment
|
||||
tests.CleanupPostgresTables(t, ctx, pool)
|
||||
|
||||
// This will execute after all tool tests complete (success, fail, or t.Fatal)
|
||||
t.Cleanup(func() {
|
||||
tests.CleanupPostgresTables(t, context.Background(), pool, uniqueID)
|
||||
})
|
||||
|
||||
//Create table names using the UUID
|
||||
tableNameParam := "param_table_" + uniqueID
|
||||
tableNameAuth := "auth_table_" + uniqueID
|
||||
tableNameTemplateParam := "template_param_table_" + uniqueID
|
||||
// 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 := tests.GetPostgresSQLParamToolInfo(tableNameParam)
|
||||
|
||||
@@ -3201,7 +3201,7 @@ func RunMySQLListTablesMissingUniqueIndexes(t *testing.T, ctx context.Context, p
|
||||
|
||||
createTableHelper := func(t *testing.T, tableName, databaseName string, primaryKey, uniqueKey, nonUniqueKey bool, ctx context.Context, pool *sql.DB) func() {
|
||||
var stmt strings.Builder
|
||||
fmt.Fprintf(&stmt, "CREATE TABLE %s (", tableName)
|
||||
stmt.WriteString(fmt.Sprintf("CREATE TABLE %s (", tableName))
|
||||
stmt.WriteString("c1 INT")
|
||||
if primaryKey {
|
||||
stmt.WriteString(" PRIMARY KEY")
|
||||
|
||||
Reference in New Issue
Block a user