Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkgs/defang/cli.nix
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ buildGo124Module {
pname = "defang-cli";
version = "git";
src = lib.cleanSource ../../src;
vendorHash = "sha256-G23v/mmyRRY2Xqq8N7knKcL4ucfBSuhgvttJ5pRKN/U=";
vendorHash = "sha256-zxQuu/RcVgA67++LuRs5xpDiq2e7gepkV8nqQ2GCR74=";

subPackages = [ "cmd/cli" ];

Expand Down
2 changes: 1 addition & 1 deletion src/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ require (
golang.org/x/term v0.38.0
google.golang.org/api v0.236.0
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217
google.golang.org/grpc v1.79.3
google.golang.org/protobuf v1.36.11
)
Expand Down Expand Up @@ -141,7 +142,6 @@ require (
go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect
golang.org/x/net v0.48.0 // indirect
google.golang.org/genai v1.30.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
gopkg.in/ini.v1 v1.66.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
4 changes: 4 additions & 0 deletions src/pkg/cli/client/byoc/gcp/byoc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ func (m MockGcpLogsClient) GetBuildInfo(ctx context.Context, buildId string) (*g
}, nil
}

func (m MockGcpLogsClient) GetInstanceGroupManagerLabels(ctx context.Context, project, region, name string) (map[string]string, error) {
Comment thread
jordanstephens marked this conversation as resolved.
return nil, nil
}

type MockGcpLoggingLister struct {
logEntries []*loggingpb.LogEntry
}
Expand Down
31 changes: 4 additions & 27 deletions src/pkg/cli/client/byoc/gcp/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,33 +285,10 @@ protoPayload.response.spec.template.metadata.labels."defang-service"=~"^(%v)$"`,
}

func (q *Query) AddComputeEngineInstanceGroupInsertOrPatch(stack, project, etag string, services []string) {
query := `protoPayload.methodName=~"beta.compute.regionInstanceGroupManagers.(insert|patch)" AND operation.first="true"`

if stack != "" {
query += fmt.Sprintf(`
protoPayload.request.allInstancesConfig.properties.labels.key="defang-stack"
protoPayload.request.allInstancesConfig.properties.labels.value="%v"`, gcp.SafeLabelValue(stack))
}

if project != "" {
query += fmt.Sprintf(`
protoPayload.request.allInstancesConfig.properties.labels.key="defang-project"
protoPayload.request.allInstancesConfig.properties.labels.value="%v"`, gcp.SafeLabelValue(project))
}

if etag != "" {
query += fmt.Sprintf(`
protoPayload.request.allInstancesConfig.properties.labels.key="defang-etag"
protoPayload.request.allInstancesConfig.properties.labels.value="%v"`, gcp.SafeLabelValue(etag))
}

if len(services) > 0 {
query += fmt.Sprintf(`
protoPayload.request.allInstancesConfig.properties.labels.key="defang-service"
protoPayload.request.allInstancesConfig.properties.labels.value=~"^(%v)$"`, servicesPattern(services))
}

q.AddQuery(query)
// Do not filter by allInstancesConfig.properties.labels here: PATCH requests only carry changed
// fields and omit labels when only the instance template is being updated. The parser reads
// labels from the live resource via GetInstanceGroupManagerLabels instead.
q.AddQuery(`protoPayload.methodName=~"beta.compute.regionInstanceGroupManagers.(insert|patch)" AND operation.first="true"`)
}

func (q *Query) AddComputeEngineInstanceGroupAddInstances() {
Expand Down
41 changes: 41 additions & 0 deletions src/pkg/cli/client/byoc/gcp/query_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package gcp

import (
"strings"
"testing"
)

func TestAddComputeEngineInstanceGroupInsertOrPatch(t *testing.T) {
tests := []struct {
name string
stack string
project string
etag string
services []string
}{
{"no args", "", "", "", nil},
{"with all args", "my-stack", "my-project", "abc123", []string{"svc1", "svc2"}},
{"with stack only", "my-stack", "", "", nil},
{"with services only", "", "", "", []string{"svc1"}},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
q := NewSubscribeQuery()
q.AddComputeEngineInstanceGroupInsertOrPatch(tt.stack, tt.project, tt.etag, tt.services)
query := q.GetQuery()

if !strings.Contains(query, `regionInstanceGroupManagers.(insert|patch)`) {
t.Errorf("query missing method name filter:\n%v", query)
}
if strings.Contains(query, "allInstancesConfig") {
t.Errorf("query must not contain allInstancesConfig label filters (PATCH requests omit labels):\n%v", query)
}
for _, label := range []string{"defang-stack", "defang-project", "defang-etag", "defang-service"} {
if strings.Contains(query, label) {
t.Errorf("query must not filter by %q label (labels absent from PATCH request body):\n%v", label, query)
}
}
})
}
}
35 changes: 18 additions & 17 deletions src/pkg/cli/client/byoc/gcp/stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type GcpLogsClient interface {
GetExecutionEnv(ctx context.Context, executionName string) (map[string]string, error)
GetProjectID() gcp.ProjectId
GetBuildInfo(ctx context.Context, buildId string) (*gcp.BuildTag, error)
GetInstanceGroupManagerLabels(ctx context.Context, project, region, name string) (map[string]string, error)
}

type ServerStream[T any] struct {
Expand Down Expand Up @@ -582,29 +583,29 @@ func getActivityParser(ctx context.Context, gcpLogsClient GcpLogsClient, waitFor
return nil, nil
}
case "gce_instance_group_manager": // Compute engine update start
request := auditLog.GetRequest()
if request == nil {
term.Warnf("missing request in audit log for instance group manager %v", path.Base(auditLog.GetResourceName()))
// The patch request body only contains changed fields (e.g. the new instance template),
// so allInstancesConfig.properties.labels is absent for updates. Read labels from the
// live resource instead using the manager name, project, and region from resource labels.
project := entry.Resource.Labels["project_id"]
region := entry.Resource.Labels["location"]
managerName := entry.Resource.Labels["instance_group_manager_name"]
labels, err := gcpLogsClient.GetInstanceGroupManagerLabels(ctx, project, region, managerName)
if err != nil {
term.Warnf("failed to get instance group manager labels for %v: %v", managerName, err)
return nil, nil
}
labels := GetListInStruct(request, "allInstancesConfig.properties.labels")
if labels == nil {
term.Warnf("missing labels in audit log for instance group manager %v", path.Base(auditLog.GetResourceName()))
serviceName := labels["defang-service"]
if serviceName == "" {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
term.Warnf("missing defang-service label in instance group manager %v", managerName)
return nil, nil
}
// Find the service name from the labels
serviceName := ""
for _, label := range labels {
fields := label.GetStructValue().GetFields()
if fields["key"].GetStringValue() == "defang-service" {
serviceName = fields["value"].GetStringValue()
break
if etag != "" {
labelEtag := labels["defang-etag"]
if labelEtag != etag {
term.Warnf("skipping instance group manager %v: etag mismatch (got %q, want %q)", managerName, labelEtag, etag)
return nil, nil
}
}
if serviceName == "" {
term.Warnf("missing defang-service label in audit log for instance group manager %v", path.Base(auditLog.GetResourceName()))
return nil, nil
}
rootTriggerId := entry.GetLabels()["compute.googleapis.com/root_trigger_id"]
if rootTriggerId == "" {
term.Warnf("missing root_trigger_id in audit log for instance group manager %v", path.Base(auditLog.GetResourceName()))
Expand Down
Loading
Loading