Skip to content

Commit 3be9b7b

Browse files
saurabh-netYuan325
andauthored
feat(tools/dataform): add dataform compile tool (googleapis#1470)
## Description This change introduces a new tool for compiling local Dataform projects. The new tool, `dataform-compile`, allows users to programmatically run the `dataform compile` command against a project on the local filesystem. This tool does not require a `source` and instead relies on the `dataform` CLI being available in the server's `PATH`. ### Changes: * Added the new tool definition in `internal/tools/dataformcompile/dataformcompile.go`. * The tool requires the following parameter: * `project_dir`: The local Dataform project directory to compile. * The tool uses `os/exec` to run the `dataform compile --json` command and parses the resulting JSON output. * Added a new integration test in `internal/tools/dataformcompile/dataformcompile_test.go` which: * Skips the test if the `dataform` CLI is not found in the `PATH`. * Uses `dataform init` to create a temporary, minimal project for testing. * Verifies success, missing parameter errors, and errors from a non-existent directory. --- > Should include a concise description of the changes (bug or feature), it's > impact, along with a summary of the solution ## PR Checklist --- > Thank you for opening a Pull Request! Before submitting your PR, there are a > few things you can do to make sure it goes smoothly: - [x] Make sure you reviewed [CONTRIBUTING.md](https://github.com/googleapis/genai-toolbox/blob/main/CONTRIBUTING.md) - [x] Make sure to open an issue as a [bug/issue](https://github.com/googleapis/genai-toolbox/issues/new/choose) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea - [x] Ensure the tests and linter pass - [x] Code coverage does not decrease (if any source code was changed) - [x] Appropriate docs were updated (if necessary) - [x] Make sure to add `!` if this involve a breaking change 🛠️ Fixes googleapis#1469 --------- Co-authored-by: Yuan Teoh <45984206+Yuan325@users.noreply.github.com>
1 parent 4dff01f commit 3be9b7b

File tree

7 files changed

+407
-0
lines changed

7 files changed

+407
-0
lines changed

.ci/integration.cloudbuild.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,26 @@ steps:
194194
dataplex \
195195
dataplex
196196
197+
- id: "dataform"
198+
name: golang:1
199+
waitFor: ["compile-test-binary"]
200+
entrypoint: /bin/bash
201+
env:
202+
- "GOPATH=/gopath"
203+
secretEnv: ["CLIENT_ID"]
204+
volumes:
205+
- name: "go"
206+
path: "/gopath"
207+
args:
208+
- -c
209+
- |
210+
apt-get update && apt-get install -y npm && \
211+
npm install -g @dataform/cli && \
212+
.ci/test_with_coverage.sh \
213+
"Dataform" \
214+
dataform \
215+
dataform
216+
197217
- id: "postgres"
198218
name: golang:1
199219
waitFor: ["compile-test-binary"]

cmd/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ import (
8080
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudsqlmysql/cloudsqlmysqlcreateinstance"
8181
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudsqlpg/cloudsqlpgcreateinstances"
8282
_ "github.com/googleapis/genai-toolbox/internal/tools/couchbase"
83+
_ "github.com/googleapis/genai-toolbox/internal/tools/dataform/dataformcompilelocal"
8384
_ "github.com/googleapis/genai-toolbox/internal/tools/dataplex/dataplexlookupentry"
8485
_ "github.com/googleapis/genai-toolbox/internal/tools/dataplex/dataplexsearchaspecttypes"
8586
_ "github.com/googleapis/genai-toolbox/internal/tools/dataplex/dataplexsearchentries"
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
title: "Dataform"
3+
type: docs
4+
weight: 1
5+
description: >
6+
Tools that work with Dataform.
7+
---
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
title: "dataform-compile-local"
3+
type: docs
4+
weight: 1
5+
description: >
6+
A "dataform-compile-local" tool runs the `dataform compile` CLI command on a local project directory.
7+
aliases:
8+
- /resources/tools/dataform-compile-local
9+
---
10+
11+
## About
12+
13+
A `dataform-compile-local` tool runs the `dataform compile` command on a local Dataform project.
14+
15+
It is a standalone tool and **is not** compatible with any sources.
16+
17+
At invocation time, the tool executes `dataform compile --json` in the specified project directory and returns the resulting JSON object from the CLI.
18+
19+
`dataform-compile-local` takes the following parameter:
20+
- `project_dir` (string): The absolute or relative path to the local Dataform project directory. The server process must have read access to this path.
21+
22+
## Requirements
23+
24+
### Dataform CLI
25+
26+
This tool executes the `dataform` command-line interface (CLI) via a system call. You must have the **`dataform` CLI** installed and available in the server's system `PATH`.
27+
28+
You can typically install the CLI via `npm`:
29+
```bash
30+
npm install -g @dataform/cli
31+
```
32+
33+
See the [official Dataform documentation](https://www.google.com/search?q=https://cloud.google.com/dataform/docs/install-dataform-cli) for more details.
34+
35+
## Example
36+
37+
```yaml
38+
tools:
39+
my_dataform_compiler:
40+
kind: dataform-compile-local
41+
description: Use this tool to compile a local Dataform project.
42+
```
43+
44+
## Reference
45+
| **field** | **type** | **required** | **description** |
46+
| :---- | :---- | :---- | :---- |
47+
| kind | string | true | Must be "dataform-compile-local". |
48+
| description | string | true | Description of the tool that is passed to the LLM. |
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package dataformcompilelocal
16+
17+
import (
18+
"context"
19+
"fmt"
20+
"os/exec"
21+
"strings"
22+
23+
"github.com/goccy/go-yaml"
24+
"github.com/googleapis/genai-toolbox/internal/sources"
25+
"github.com/googleapis/genai-toolbox/internal/tools"
26+
)
27+
28+
const kind string = "dataform-compile-local"
29+
30+
func init() {
31+
if !tools.Register(kind, newConfig) {
32+
panic(fmt.Sprintf("tool kind %q already registered", kind))
33+
}
34+
}
35+
36+
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
37+
actual := Config{Name: name}
38+
if err := decoder.DecodeContext(ctx, &actual); err != nil {
39+
return nil, err
40+
}
41+
return actual, nil
42+
}
43+
44+
type Config struct {
45+
Name string `yaml:"name" validate:"required"`
46+
Kind string `yaml:"kind" validate:"required"`
47+
Description string `yaml:"description" validate:"required"`
48+
AuthRequired []string `yaml:"authRequired"`
49+
}
50+
51+
var _ tools.ToolConfig = Config{}
52+
53+
func (cfg Config) ToolConfigKind() string {
54+
return kind
55+
}
56+
57+
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
58+
allParameters := tools.Parameters{
59+
tools.NewStringParameter("project_dir", "The Dataform project directory."),
60+
}
61+
paramManifest := allParameters.Manifest()
62+
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters)
63+
64+
t := Tool{
65+
Name: cfg.Name,
66+
Kind: kind,
67+
AuthRequired: cfg.AuthRequired,
68+
Parameters: allParameters,
69+
manifest: tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired},
70+
mcpManifest: mcpManifest,
71+
}
72+
73+
return t, nil
74+
}
75+
76+
var _ tools.Tool = Tool{}
77+
78+
type Tool struct {
79+
Name string `yaml:"name"`
80+
Kind string `yaml:"kind"`
81+
AuthRequired []string `yaml:"authRequired"`
82+
Parameters tools.Parameters `yaml:"allParams"`
83+
manifest tools.Manifest
84+
mcpManifest tools.McpManifest
85+
}
86+
87+
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
88+
paramsMap := params.AsMap()
89+
90+
projectDir, ok := paramsMap["project_dir"].(string)
91+
if !ok || projectDir == "" {
92+
return nil, fmt.Errorf("error casting 'project_dir' to string or invalid value")
93+
}
94+
95+
cmd := exec.CommandContext(ctx, "dataform", "compile", projectDir, "--json")
96+
output, err := cmd.CombinedOutput()
97+
if err != nil {
98+
return nil, fmt.Errorf("error executing dataform compile: %w\nOutput: %s", err, string(output))
99+
}
100+
101+
return strings.TrimSpace(string(output)), nil
102+
}
103+
104+
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
105+
return tools.ParseParams(t.Parameters, data, claims)
106+
}
107+
108+
func (t Tool) Manifest() tools.Manifest {
109+
return t.manifest
110+
}
111+
112+
func (t Tool) McpManifest() tools.McpManifest {
113+
return t.mcpManifest
114+
}
115+
116+
func (t Tool) Authorized(verifiedAuthServices []string) bool {
117+
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
118+
}
119+
120+
func (t Tool) RequiresClientAuthorization() bool {
121+
return false
122+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package dataformcompilelocal_test
16+
17+
import (
18+
"testing"
19+
20+
yaml "github.com/goccy/go-yaml"
21+
"github.com/google/go-cmp/cmp"
22+
"github.com/googleapis/genai-toolbox/internal/server"
23+
"github.com/googleapis/genai-toolbox/internal/testutils"
24+
"github.com/googleapis/genai-toolbox/internal/tools/dataform/dataformcompilelocal"
25+
)
26+
27+
func TestParseFromYamlDataformCompile(t *testing.T) {
28+
ctx, err := testutils.ContextWithNewLogger()
29+
if err != nil {
30+
t.Fatalf("unexpected error: %s", err)
31+
}
32+
tcs := []struct {
33+
desc string
34+
in string
35+
want server.ToolConfigs
36+
}{
37+
{
38+
desc: "basic example",
39+
in: `
40+
tools:
41+
example_tool:
42+
kind: dataform-compile-local
43+
description: some description
44+
`,
45+
want: server.ToolConfigs{
46+
"example_tool": dataformcompilelocal.Config{
47+
Name: "example_tool",
48+
Kind: "dataform-compile-local",
49+
Description: "some description",
50+
AuthRequired: []string{},
51+
},
52+
},
53+
},
54+
}
55+
for _, tc := range tcs {
56+
t.Run(tc.desc, func(t *testing.T) {
57+
got := struct {
58+
Tools server.ToolConfigs `yaml:"tools"`
59+
}{}
60+
// Parse contents
61+
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
62+
if err != nil {
63+
t.Fatalf("unable to unmarshal: %s", err)
64+
}
65+
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
66+
t.Fatalf("incorrect parse: diff %v", diff)
67+
}
68+
})
69+
}
70+
71+
}

0 commit comments

Comments
 (0)