Skip to content

Commit d0d134c

Browse files
authored
add new mcp tools for cloud build (#76)
* add new mcp tools for cloud build * replace logging with api
1 parent 79ad5c5 commit d0d134c

File tree

3 files changed

+166
-3
lines changed

3 files changed

+166
-3
lines changed

devops-mcp-server/cloudbuild/client/cloudbuildclient.go

Lines changed: 101 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,14 @@ import (
2020
"strings"
2121

2222
cloudbuild "cloud.google.com/go/cloudbuild/apiv1/v2"
23-
cloudbuildpb "cloud.google.com/go/cloudbuild/apiv1/v2/cloudbuildpb"
24-
23+
logging "cloud.google.com/go/logging/apiv2"
2524
build "google.golang.org/api/cloudbuild/v1"
2625
"google.golang.org/api/iterator"
26+
"google.golang.org/protobuf/encoding/protojson"
2727
"google.golang.org/protobuf/types/known/timestamppb"
28+
29+
cloudbuildpb "cloud.google.com/go/cloudbuild/apiv1/v2/cloudbuildpb"
30+
loggingpb "cloud.google.com/go/logging/apiv2/loggingpb"
2831
)
2932

3033
// contextKey is a private type to use as a key for context values.
@@ -51,6 +54,14 @@ type CloudBuildClient interface {
5154
GetLatestBuildForTrigger(ctx context.Context, projectID, location, triggerID string) (*cloudbuildpb.Build, error)
5255
ListBuildTriggers(ctx context.Context, projectID, location string) ([]*cloudbuildpb.BuildTrigger, error)
5356
RunBuildTrigger(ctx context.Context, projectID, location, triggerID, branch, tag, commitSha string) (*cloudbuild.RunBuildTriggerOperation, error)
57+
ListBuilds(ctx context.Context, projectID, location string) ([]*cloudbuildpb.Build, error)
58+
GetBuildInfo(ctx context.Context, projectID, location, buildID string) (BuildInfo, error)
59+
StartBuild(ctx context.Context, projectID, location string, source *cloudbuildpb.Source) (*cloudbuild.CreateBuildOperation, error)
60+
}
61+
62+
type BuildInfo struct {
63+
BuildDetails *cloudbuildpb.Build
64+
Logs string
5465
}
5566

5667
// NewCloudBuildClient creates a new Cloud Build client.
@@ -65,13 +76,23 @@ func NewCloudBuildClient(ctx context.Context) (CloudBuildClient, error) {
6576
return nil, fmt.Errorf("failed to create Cloud Build service: %v", err)
6677
}
6778

68-
return &CloudBuildClientImpl{v1client: c, legacyClient: c2}, nil
79+
loggingClient, err := logging.NewClient(ctx)
80+
if err != nil {
81+
return nil, fmt.Errorf("failed to create Logging client: %v", err)
82+
}
83+
84+
return &CloudBuildClientImpl{
85+
v1client: c,
86+
legacyClient: c2,
87+
loggingClient: loggingClient,
88+
}, nil
6989
}
7090

7191
// CloudBuildClientImpl is an implementation of the CloudBuildClient interface.
7292
type CloudBuildClientImpl struct {
7393
v1client *cloudbuild.Client
7494
legacyClient *build.Service
95+
loggingClient *logging.Client
7596
}
7697

7798
// CreateCloudBuildTrigger creates a new build trigger.
@@ -181,3 +202,80 @@ func (c *CloudBuildClientImpl) RunBuildTrigger(ctx context.Context, projectID, l
181202
}
182203
return op, nil
183204
}
205+
206+
207+
func (c *CloudBuildClientImpl) ListBuilds(ctx context.Context, projectID, location string) ([]*cloudbuildpb.Build, error) {
208+
req := &cloudbuildpb.ListBuildsRequest{
209+
Parent: fmt.Sprintf("projects/%s/locations/%s", projectID, location),
210+
}
211+
it := c.v1client.ListBuilds(ctx, req)
212+
var builds []*cloudbuildpb.Build
213+
for {
214+
build, err := it.Next()
215+
if err == iterator.Done {
216+
break
217+
}
218+
if err != nil {
219+
return nil, fmt.Errorf("failed to list builds: %w", err)
220+
}
221+
builds = append(builds, build)
222+
}
223+
return builds, nil
224+
}
225+
226+
func (c *CloudBuildClientImpl) GetBuildInfo(ctx context.Context, projectID, location, buildID string) (BuildInfo, error) {
227+
req := &cloudbuildpb.GetBuildRequest{
228+
Name: fmt.Sprintf("projects/%s/locations/%s/builds/%s", projectID, location, buildID),
229+
}
230+
build, err := c.v1client.GetBuild(ctx, req)
231+
if err != nil {
232+
return BuildInfo{}, fmt.Errorf("failed to get build info: %w", err)
233+
}
234+
info := BuildInfo{BuildDetails: build}
235+
logReq := &loggingpb.ListLogEntriesRequest{
236+
ResourceNames: []string{fmt.Sprintf("projects/%s", projectID)},
237+
Filter: fmt.Sprintf(`resource.type="build" AND resource.labels.build_id="%s" AND logName="projects/%s/logs/cloudbuild"`, buildID, projectID),
238+
}
239+
it := c.loggingClient.ListLogEntries(ctx, logReq)
240+
var logs []string
241+
for {
242+
entry, err := it.Next()
243+
if err == iterator.Done {
244+
break
245+
}
246+
if err != nil {
247+
return BuildInfo{}, fmt.Errorf("failed to list log entries: %w", err)
248+
}
249+
var logMessage string
250+
switch payload := entry.Payload.(type) {
251+
case *loggingpb.LogEntry_TextPayload:
252+
logMessage = payload.TextPayload
253+
case *loggingpb.LogEntry_JsonPayload:
254+
jsonBytes, err := protojson.Marshal(payload.JsonPayload)
255+
if err != nil {
256+
logMessage = fmt.Sprintf("failed to marshal json payload to string: %v", err)
257+
} else {
258+
logMessage = string(jsonBytes)
259+
}
260+
case *loggingpb.LogEntry_ProtoPayload:
261+
logMessage = fmt.Sprintf("%v", payload.ProtoPayload)
262+
default:
263+
return BuildInfo{}, fmt.Errorf("unknown log entry payload type")
264+
}
265+
logs = append(logs, logMessage)
266+
}
267+
info.Logs = strings.Join(logs, "\n")
268+
return info, nil
269+
}
270+
271+
func (c *CloudBuildClientImpl) StartBuild(ctx context.Context, projectID, location string, source *cloudbuildpb.Source) (*cloudbuild.CreateBuildOperation, error) {
272+
req := &cloudbuildpb.CreateBuildRequest{
273+
Parent: fmt.Sprintf("projects/%s/locations/%s", projectID, location),
274+
Build: &cloudbuildpb.Build{Source: source},
275+
}
276+
ops, err := c.v1client.CreateBuild(ctx, req)
277+
if err != nil {
278+
return nil, fmt.Errorf("failed to start build: %w", err)
279+
}
280+
return ops, nil
281+
}

devops-mcp-server/cloudbuild/cloudbuild.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import (
2525
cloudbuildclient "devops-mcp-server/cloudbuild/client"
2626
iamclient "devops-mcp-server/iam/client"
2727
resourcemanagerclient "devops-mcp-server/resourcemanager/client"
28+
29+
cloudbuildpb "cloud.google.com/go/cloudbuild/apiv1/v2/cloudbuildpb"
2830
)
2931

3032
// Handler holds the clients for the cloudbuild service.
@@ -39,6 +41,9 @@ func (h *Handler) Register(server *mcp.Server) {
3941
addCreateTriggerTool(server, h.CbClient, h.IClient, h.RClient)
4042
addRunTriggerTool(server, h.CbClient)
4143
addListTriggersTool(server, h.CbClient)
44+
addListBuildsTool(server, h.CbClient)
45+
addGetBuildInfoTool(server, h.CbClient)
46+
addStartBuildTool(server, h.CbClient)
4247
}
4348

4449
type RunTriggerArgs struct {
@@ -146,3 +151,62 @@ func IsValidServiceAccount(sa string) bool {
146151
var saRegex = regexp.MustCompile(`^serviceAccount:[a-z0-9-]+@[a-z0-9-]+\.iam\.gserviceaccount\.com$`)
147152
return saRegex.MatchString(sa)
148153
}
154+
155+
type ListBuildsArgs struct {
156+
ProjectID string `json:"project_id" jsonschema:"The Google Cloud project ID."`
157+
Location string `json:"location" jsonschema:"The Google Cloud location for the builds."`
158+
}
159+
160+
type GetBuildInfoArgs struct {
161+
ProjectID string `json:"project_id" jsonschema:"The Google Cloud project ID."`
162+
Location string `json:"location" jsonschema:"The Google Cloud location for the build."`
163+
BuildID string `json:"build_id" jsonschema:"The ID of the build."`
164+
}
165+
166+
type StartBuildArgs struct {
167+
ProjectID string `json:"project_id" jsonschema:"The Google Cloud project ID."`
168+
Location string `json:"location" jsonschema:"The Google Cloud location for the build."`
169+
Bucket string `json:"bucket" jsonschema:"The Cloud Storage bucket where the source is located."`
170+
Object string `json:"object" jsonschema:"The Cloud Storage object (file) where the source is located."`
171+
}
172+
173+
func addListBuildsTool(server *mcp.Server, cbClient cloudbuildclient.CloudBuildClient) {
174+
listBuildsToolFunc := func(ctx context.Context, req *mcp.CallToolRequest, args ListBuildsArgs) (*mcp.CallToolResult, any, error) {
175+
res, err := cbClient.ListBuilds(ctx, args.ProjectID, args.Location)
176+
if err != nil {
177+
return &mcp.CallToolResult{}, nil, fmt.Errorf("failed to list builds: %w", err)
178+
}
179+
return &mcp.CallToolResult{}, map[string]any{"builds": res}, nil
180+
}
181+
mcp.AddTool(server, &mcp.Tool{Name: "cloudbuild.list_builds", Description: "Lists all Cloud Builds in a given location and project."}, listBuildsToolFunc)
182+
}
183+
184+
func addGetBuildInfoTool(server *mcp.Server, cbClient cloudbuildclient.CloudBuildClient) {
185+
getBuildInfoToolFunc := func(ctx context.Context, req *mcp.CallToolRequest, args GetBuildInfoArgs) (*mcp.CallToolResult, any, error) {
186+
res, err := cbClient.GetBuildInfo(ctx, args.ProjectID, args.Location, args.BuildID)
187+
if err != nil {
188+
return &mcp.CallToolResult{}, nil, fmt.Errorf("failed to get build info: %w", err)
189+
}
190+
return &mcp.CallToolResult{}, res, nil
191+
}
192+
mcp.AddTool(server, &mcp.Tool{Name: "cloudbuild.get_build_info", Description: "Gets information about a specific Cloud Build."}, getBuildInfoToolFunc)
193+
}
194+
195+
func addStartBuildTool(server *mcp.Server, cbClient cloudbuildclient.CloudBuildClient) {
196+
startBuildToolFunc := func(ctx context.Context, req *mcp.CallToolRequest, args StartBuildArgs) (*mcp.CallToolResult, any, error) {
197+
source := &cloudbuildpb.Source{
198+
Source: &cloudbuildpb.Source_StorageSource{
199+
StorageSource: &cloudbuildpb.StorageSource{
200+
Bucket: args.Bucket,
201+
Object: args.Object,
202+
},
203+
},
204+
}
205+
res, err := cbClient.StartBuild(ctx, args.ProjectID, args.Location, source)
206+
if err != nil {
207+
return &mcp.CallToolResult{}, nil, fmt.Errorf("failed to start build: %w", err)
208+
}
209+
return &mcp.CallToolResult{}, res, nil
210+
}
211+
mcp.AddTool(server, &mcp.Tool{Name: "cloudbuild.start_build", Description: "Starts a new Cloud Build from a source in Google Cloud Storage."}, startBuildToolFunc)
212+
}

devops-mcp-server/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ require (
77
cloud.google.com/go/auth v0.17.0
88
cloud.google.com/go/cloudbuild v1.23.1
99
cloud.google.com/go/iam v1.5.3
10+
cloud.google.com/go/logging v1.13.0
1011
cloud.google.com/go/resourcemanager v1.10.7
1112
cloud.google.com/go/run v1.12.1
1213
cloud.google.com/go/storage v1.57.0

0 commit comments

Comments
 (0)