From 68360108cff7174eb5feec8e742ebe7aab187c7e Mon Sep 17 00:00:00 2001 From: jordanstephens Date: Thu, 5 Feb 2026 13:58:50 -0800 Subject: [PATCH 1/6] sync README --- pkgs/npm/README.md | 2 +- src/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/npm/README.md b/pkgs/npm/README.md index 99006010f..323ab90a1 100644 --- a/pkgs/npm/README.md +++ b/pkgs/npm/README.md @@ -10,7 +10,7 @@ The Defang Command-Line Interface [(CLI)](https://docs.defang.io/docs/getting-st - Read our [Getting Started](https://docs.defang.io/docs/getting-started) page - Follow the installation instructions from the [Installing](https://docs.defang.io/docs/getting-started/installing) page -- Take a look at our [Samples folder](https://github.com/DefangLabs/defang/tree/main/samples) for example projects in various programming languages. +- Take a look at our [Samples](https://github.com/DefangLabs/samples) in Golang, Python, and Node.js that show how to accomplish various tasks and deploy them - Try the AI integration by running `defang generate` - Start your new service with `defang compose up` diff --git a/src/README.md b/src/README.md index 99006010f..323ab90a1 100644 --- a/src/README.md +++ b/src/README.md @@ -10,7 +10,7 @@ The Defang Command-Line Interface [(CLI)](https://docs.defang.io/docs/getting-st - Read our [Getting Started](https://docs.defang.io/docs/getting-started) page - Follow the installation instructions from the [Installing](https://docs.defang.io/docs/getting-started/installing) page -- Take a look at our [Samples folder](https://github.com/DefangLabs/defang/tree/main/samples) for example projects in various programming languages. +- Take a look at our [Samples](https://github.com/DefangLabs/samples) in Golang, Python, and Node.js that show how to accomplish various tasks and deploy them - Try the AI integration by running `defang generate` - Start your new service with `defang compose up` From 2095b61963ab8b857e640db47858fe548ba9f5d4 Mon Sep 17 00:00:00 2001 From: Jordan Stephens Date: Mon, 19 Jan 2026 13:15:20 -0800 Subject: [PATCH 2/6] sketch out codebuild events --- src/pkg/cli/client/byoc/aws/byoc.go | 32 +++++++++--- src/pkg/cli/client/byoc/aws/stream.go | 11 ++-- src/pkg/cli/client/byoc/aws/stream_test.go | 2 +- src/pkg/cli/client/byoc/aws/subscribe.go | 12 +++++ src/pkg/clouds/aws/codebuild/event.go | 59 ++++++++++++++++++++++ 5 files changed, 106 insertions(+), 10 deletions(-) create mode 100644 src/pkg/clouds/aws/codebuild/event.go diff --git a/src/pkg/cli/client/byoc/aws/byoc.go b/src/pkg/cli/client/byoc/aws/byoc.go index ed616ce88..4077c23b6 100644 --- a/src/pkg/cli/client/byoc/aws/byoc.go +++ b/src/pkg/cli/client/byoc/aws/byoc.go @@ -21,6 +21,7 @@ import ( "github.com/DefangLabs/defang/src/pkg/cli/compose" "github.com/DefangLabs/defang/src/pkg/clouds" "github.com/DefangLabs/defang/src/pkg/clouds/aws" + "github.com/DefangLabs/defang/src/pkg/clouds/aws/codebuild" "github.com/DefangLabs/defang/src/pkg/clouds/aws/cw" "github.com/DefangLabs/defang/src/pkg/clouds/aws/ecs" "github.com/DefangLabs/defang/src/pkg/clouds/aws/ecs/cfn" @@ -54,11 +55,12 @@ type ByocAws struct { driver *cfn.AwsEcsCfn // TODO: ecs is stateful, contains the output of the cd cfn stack after SetUpCD - ecsEventHandlers []ECSEventHandler - handlersLock sync.RWMutex - cdEtag types.ETag - cdStart time.Time - cdTaskArn ecs.TaskArn + ecsEventHandlers []ECSEventHandler + codebuildEventHandlers []CodebuildEventHandler + handlersLock sync.RWMutex + cdEtag types.ETag + cdStart time.Time + cdTaskArn ecs.TaskArn needDockerHubCreds bool } @@ -709,7 +711,7 @@ func (b *ByocAws) QueryLogs(ctx context.Context, req *defangv1.TailRequest) (cli if err != nil { return nil, AnnotateAwsError(err) } - return newByocServerStream(tailStream, etag, req.Services, b), nil + return newByocServerStream(tailStream, etag, req.Services, b, b), nil } func (b *ByocAws) queryCdLogs(ctx context.Context, cwClient *cloudwatchlogs.Client, req *defangv1.TailRequest) (cw.LiveTailStream, error) { @@ -890,6 +892,10 @@ type ECSEventHandler interface { HandleECSEvent(evt ecs.Event) } +type CodebuildEventHandler interface { + HandleCodebuildEvent(evt codebuild.Event) +} + func (b *ByocAws) Subscribe(ctx context.Context, req *defangv1.SubscribeRequest) (client.ServerStream[defangv1.SubscribeResponse], error) { s := &byocSubscribeServerStream{ services: req.Services, @@ -910,12 +916,26 @@ func (b *ByocAws) HandleECSEvent(evt ecs.Event) { } } +func (b *ByocAws) HandleCodebuildEvent(evt codebuild.Event) { + b.handlersLock.RLock() + defer b.handlersLock.RUnlock() + for _, handler := range b.codebuildEventHandlers { + handler.HandleCodebuildEvent(evt) + } +} + func (b *ByocAws) AddEcsEventHandler(handler ECSEventHandler) { b.handlersLock.Lock() defer b.handlersLock.Unlock() b.ecsEventHandlers = append(b.ecsEventHandlers, handler) } +func (b *ByocAws) AddCodebuildEventHandler(handler CodebuildEventHandler) { + b.handlersLock.Lock() + defer b.handlersLock.Unlock() + b.codebuildEventHandlers = append(b.codebuildEventHandlers, handler) +} + func (b *ByocAws) GetPrivateDomain(projectName string) string { return b.GetProjectLabel(projectName) + ".internal" } diff --git a/src/pkg/cli/client/byoc/aws/stream.go b/src/pkg/cli/client/byoc/aws/stream.go index c3835c5db..5345cb40d 100644 --- a/src/pkg/cli/client/byoc/aws/stream.go +++ b/src/pkg/cli/client/byoc/aws/stream.go @@ -10,6 +10,7 @@ import ( "github.com/DefangLabs/defang/src/pkg/cli/client" "github.com/DefangLabs/defang/src/pkg/cli/client/byoc" + "github.com/DefangLabs/defang/src/pkg/clouds/aws/codebuild" "github.com/DefangLabs/defang/src/pkg/clouds/aws/cw" "github.com/DefangLabs/defang/src/pkg/clouds/aws/ecs" "github.com/DefangLabs/defang/src/pkg/logs" @@ -29,16 +30,18 @@ type byocServerStream struct { services []string stream cw.LiveTailStream - ecsEventsHandler ECSEventHandler + ecsEventsHandler ECSEventHandler + codebuildEventHandler CodebuildEventHandler } -func newByocServerStream(stream cw.LiveTailStream, etag string, services []string, ecsEventHandler ECSEventHandler) *byocServerStream { +func newByocServerStream(stream cw.LiveTailStream, etag string, services []string, ecsEventHandler ECSEventHandler, codebuildEventHandler CodebuildEventHandler) *byocServerStream { return &byocServerStream{ etag: etag, stream: stream, services: services, - ecsEventsHandler: ecsEventHandler, + ecsEventsHandler: ecsEventHandler, + codebuildEventHandler: codebuildEventHandler, } } @@ -179,6 +182,8 @@ func (bs *byocServerStream) parseEvents(events []cw.LogEvent) *defangv1.TailResp entry.Service = response.Service entry.Etag = response.Etag entry.Host = response.Host + evt := codebuild.ParseCodebuildEvent(entry) + bs.codebuildEventHandler.HandleCodebuildEvent(evt) } else if (response.Service == "cd") && (strings.HasPrefix(entry.Message, logs.ErrorPrefix) || strings.Contains(strings.ToLower(entry.Message), "error:")) { entry.Stderr = true } diff --git a/src/pkg/cli/client/byoc/aws/stream_test.go b/src/pkg/cli/client/byoc/aws/stream_test.go index 9e417921c..2d9926279 100644 --- a/src/pkg/cli/client/byoc/aws/stream_test.go +++ b/src/pkg/cli/client/byoc/aws/stream_test.go @@ -143,7 +143,7 @@ func TestStreamToLogEvent(t *testing.T) { }, } - var byocServiceStream = newByocServerStream(nil, testEtag, []string{"cd", "app", "django", "django-image"}, nil) + var byocServiceStream = newByocServerStream(nil, testEtag, []string{"cd", "app", "django", "django-image"}, nil, nil) for _, td := range testdata { tailResp := byocServiceStream.parseEvents([]cw.LogEvent{*td.event}) diff --git a/src/pkg/cli/client/byoc/aws/subscribe.go b/src/pkg/cli/client/byoc/aws/subscribe.go index fe891a6fc..35ff1a56e 100644 --- a/src/pkg/cli/client/byoc/aws/subscribe.go +++ b/src/pkg/cli/client/byoc/aws/subscribe.go @@ -18,6 +18,18 @@ type byocSubscribeServerStream struct { done chan struct{} } +func (s *byocSubscribeServerStream) HandleCodebuildEvent(evt ecs.Event) { + resp := defangv1.SubscribeResponse{ + Name: evt.Service(), + Status: evt.Status(), + State: evt.State(), + } + select { + case s.ch <- &resp: + case <-s.done: + } +} + func (s *byocSubscribeServerStream) HandleECSEvent(evt ecs.Event) { if etag := evt.Etag(); etag == "" || etag != s.etag { return diff --git a/src/pkg/clouds/aws/codebuild/event.go b/src/pkg/clouds/aws/codebuild/event.go new file mode 100644 index 000000000..9c588b806 --- /dev/null +++ b/src/pkg/clouds/aws/codebuild/event.go @@ -0,0 +1,59 @@ +package codebuild + +import ( + "time" + + defangv1 "github.com/DefangLabs/defang/src/protos/io/defang/v1" +) + +type Event interface { + Service() string + Etag() string + Host() string + Status() string + State() defangv1.ServiceState +} + +type eventCommonFields struct { + Account string `json:"account"` + DetailType string `json:"detail-type"` + Id string `json:"id"` + Region string `json:"region"` + Resources []string `json:"resources"` + Source string `json:"source"` + Time time.Time `json:"time"` + Version string `json:"version"` +} + +type CodebuildEvent struct { + eventCommonFields +} + +func ParseCodebuildEvent(entry *defangv1.LogEntry) Event { + // TODO: implement parsing of CodeBuild events from log entries + return &CodebuildEvent{} +} + +func (e *CodebuildEvent) State() defangv1.ServiceState { + // TODO: implement mapping of CodeBuild event details to ServiceState + return defangv1.ServiceState_NOT_SPECIFIED +} + +func (e *CodebuildEvent) Service() string { + // TODO: implement extraction of service name from CodeBuild event details + return "" +} + +func (e *CodebuildEvent) Etag() string { + // TODO: implement extraction of etag from CodeBuild event details + return "" +} + +func (e *CodebuildEvent) Host() string { + return "codebuild" +} + +func (e *CodebuildEvent) Status() string { + // TODO: implement extraction of status from CodeBuild event details + return "" +} From c0ef31e28c20def42c1456ff9ffec58d901a4055 Mon Sep 17 00:00:00 2001 From: Jordan Stephens Date: Mon, 19 Jan 2026 17:19:56 -0800 Subject: [PATCH 3/6] parse and emit codebuild events --- src/pkg/cli/client/byoc/aws/byoc.go | 1 + src/pkg/cli/client/byoc/aws/stream.go | 10 +- src/pkg/cli/client/byoc/aws/subscribe.go | 3 +- src/pkg/clouds/aws/codebuild/event.go | 78 ++++++++-- src/pkg/clouds/aws/codebuild/event_test.go | 171 +++++++++++++++++++++ 5 files changed, 242 insertions(+), 21 deletions(-) create mode 100644 src/pkg/clouds/aws/codebuild/event_test.go diff --git a/src/pkg/cli/client/byoc/aws/byoc.go b/src/pkg/cli/client/byoc/aws/byoc.go index 4077c23b6..7bac2f776 100644 --- a/src/pkg/cli/client/byoc/aws/byoc.go +++ b/src/pkg/cli/client/byoc/aws/byoc.go @@ -905,6 +905,7 @@ func (b *ByocAws) Subscribe(ctx context.Context, req *defangv1.SubscribeRequest) done: make(chan struct{}), } b.AddEcsEventHandler(s) + b.AddCodebuildEventHandler(s) return s, nil } diff --git a/src/pkg/cli/client/byoc/aws/stream.go b/src/pkg/cli/client/byoc/aws/stream.go index 5345cb40d..879e4a350 100644 --- a/src/pkg/cli/client/byoc/aws/stream.go +++ b/src/pkg/cli/client/byoc/aws/stream.go @@ -172,18 +172,22 @@ func (bs *byocServerStream) parseEvents(events []cw.LogEvent) *defangv1.TailResp if err != nil { term.Debugf("error parsing ECS event, output raw event log: %v", err) } else { - bs.ecsEventsHandler.HandleECSEvent(evt) + if bs.ecsEventsHandler != nil { + bs.ecsEventsHandler.HandleECSEvent(evt) + } entry.Service = evt.Service() entry.Etag = evt.Etag() entry.Host = evt.Host() entry.Message = evt.Status() } } else if parseCodeBuildRecords { - entry.Service = response.Service + entry.Service = strings.TrimSuffix(response.Service, "-image") entry.Etag = response.Etag entry.Host = response.Host evt := codebuild.ParseCodebuildEvent(entry) - bs.codebuildEventHandler.HandleCodebuildEvent(evt) + if bs.codebuildEventHandler != nil { + bs.codebuildEventHandler.HandleCodebuildEvent(evt) + } } else if (response.Service == "cd") && (strings.HasPrefix(entry.Message, logs.ErrorPrefix) || strings.Contains(strings.ToLower(entry.Message), "error:")) { entry.Stderr = true } diff --git a/src/pkg/cli/client/byoc/aws/subscribe.go b/src/pkg/cli/client/byoc/aws/subscribe.go index 35ff1a56e..c89b2266a 100644 --- a/src/pkg/cli/client/byoc/aws/subscribe.go +++ b/src/pkg/cli/client/byoc/aws/subscribe.go @@ -3,6 +3,7 @@ package aws import ( "slices" + "github.com/DefangLabs/defang/src/pkg/clouds/aws/codebuild" "github.com/DefangLabs/defang/src/pkg/clouds/aws/ecs" "github.com/DefangLabs/defang/src/pkg/types" defangv1 "github.com/DefangLabs/defang/src/protos/io/defang/v1" @@ -18,7 +19,7 @@ type byocSubscribeServerStream struct { done chan struct{} } -func (s *byocSubscribeServerStream) HandleCodebuildEvent(evt ecs.Event) { +func (s *byocSubscribeServerStream) HandleCodebuildEvent(evt codebuild.Event) { resp := defangv1.SubscribeResponse{ Name: evt.Service(), Status: evt.Status(), diff --git a/src/pkg/clouds/aws/codebuild/event.go b/src/pkg/clouds/aws/codebuild/event.go index 9c588b806..0ec09e269 100644 --- a/src/pkg/clouds/aws/codebuild/event.go +++ b/src/pkg/clouds/aws/codebuild/event.go @@ -1,6 +1,7 @@ package codebuild import ( + "strings" "time" defangv1 "github.com/DefangLabs/defang/src/protos/io/defang/v1" @@ -15,38 +16,48 @@ type Event interface { } type eventCommonFields struct { - Account string `json:"account"` - DetailType string `json:"detail-type"` - Id string `json:"id"` - Region string `json:"region"` - Resources []string `json:"resources"` - Source string `json:"source"` - Time time.Time `json:"time"` - Version string `json:"version"` + Account string + DetailType string + Id string + Region string + Resources []string + Source string + Time time.Time + Version string } type CodebuildEvent struct { eventCommonFields + message string + service string + etag string + host string + state defangv1.ServiceState } func ParseCodebuildEvent(entry *defangv1.LogEntry) Event { - // TODO: implement parsing of CodeBuild events from log entries - return &CodebuildEvent{} + message := entry.Message + state := parseCodebuildMessage(message) + + return &CodebuildEvent{ + message: message, + service: entry.Service, + etag: entry.Etag, + host: entry.Host, + state: state, + } } func (e *CodebuildEvent) State() defangv1.ServiceState { - // TODO: implement mapping of CodeBuild event details to ServiceState - return defangv1.ServiceState_NOT_SPECIFIED + return e.state } func (e *CodebuildEvent) Service() string { - // TODO: implement extraction of service name from CodeBuild event details - return "" + return e.service } func (e *CodebuildEvent) Etag() string { - // TODO: implement extraction of etag from CodeBuild event details - return "" + return e.etag } func (e *CodebuildEvent) Host() string { @@ -54,6 +65,39 @@ func (e *CodebuildEvent) Host() string { } func (e *CodebuildEvent) Status() string { - // TODO: implement extraction of status from CodeBuild event details return "" } + +func parseCodebuildMessage(message string) defangv1.ServiceState { + if strings.Contains(message, "Phase complete: ") && strings.Contains(message, "State: FAILED") { + return defangv1.ServiceState_BUILD_FAILED + } + if strings.Contains(message, "Running on CodeBuild") { + return defangv1.ServiceState_BUILD_ACTIVATING + } + if strings.Contains(message, "Phase is DOWNLOAD_SOURCE") { + return defangv1.ServiceState_BUILD_RUNNING + } + + if strings.Contains(message, "Entering phase INSTALL") { + return defangv1.ServiceState_BUILD_RUNNING + } + + if strings.Contains(message, "Entering phase PRE_BUILD") { + return defangv1.ServiceState_BUILD_RUNNING + } + + if strings.Contains(message, "Entering phase BUILD") { + return defangv1.ServiceState_BUILD_RUNNING + } + + if strings.Contains(message, "Entering phase POST_BUILD") { + return defangv1.ServiceState_BUILD_STOPPING + } + + if strings.Contains(message, "Phase complete: UPLOAD_ARTIFACTS State: SUCCEEDED") { + return defangv1.ServiceState_DEPLOYMENT_PENDING + } + + return defangv1.ServiceState_NOT_SPECIFIED +} diff --git a/src/pkg/clouds/aws/codebuild/event_test.go b/src/pkg/clouds/aws/codebuild/event_test.go new file mode 100644 index 000000000..f74030aa5 --- /dev/null +++ b/src/pkg/clouds/aws/codebuild/event_test.go @@ -0,0 +1,171 @@ +package codebuild + +import ( + "testing" + + defangv1 "github.com/DefangLabs/defang/src/protos/io/defang/v1" +) + +func TestParseCodebuildEvent(t *testing.T) { + tests := []struct { + name string + logEntry *defangv1.LogEntry + expected Event + expectedState defangv1.ServiceState + }{ + { + name: "Basic CodeBuild Event", + logEntry: &defangv1.LogEntry{ + Message: "Running on CodeBuild", + Service: "my-service", + Etag: "etag-123", + Host: "host-abc", + }, + expected: &CodebuildEvent{ + message: "Running on CodeBuild", + service: "my-service", + etag: "etag-123", + host: "codebuild", + state: defangv1.ServiceState_BUILD_ACTIVATING, + }, + expectedState: defangv1.ServiceState_BUILD_ACTIVATING, + }, + { + name: "Build Failed Event", + logEntry: &defangv1.LogEntry{ + Message: "Phase complete: BUILD State: FAILED", + Service: "failing-service", + Etag: "etag-456", + Host: "host-def", + }, + expected: &CodebuildEvent{ + message: "Phase complete: BUILD State: FAILED", + service: "failing-service", + etag: "etag-456", + host: "codebuild", + state: defangv1.ServiceState_BUILD_FAILED, + }, + expectedState: defangv1.ServiceState_BUILD_FAILED, + }, + { + name: "Build Succeeded Event", + logEntry: &defangv1.LogEntry{ + Message: "Phase complete: UPLOAD_ARTIFACTS State: SUCCEEDED", + Service: "successful-service", + Etag: "etag-789", + Host: "host-ghi", + }, + expected: &CodebuildEvent{ + message: "Phase complete: UPLOAD_ARTIFACTS State: SUCCEEDED", + service: "successful-service", + etag: "etag-789", + host: "codebuild", + state: defangv1.ServiceState_DEPLOYMENT_PENDING, + }, + expectedState: defangv1.ServiceState_DEPLOYMENT_PENDING, + }, + { + name: "Unknown Event", + logEntry: &defangv1.LogEntry{ + Message: "Some unrelated log message", + Service: "unknown-service", + Etag: "etag-000", + Host: "host-xyz", + }, + expected: &CodebuildEvent{ + message: "Some unrelated log message", + service: "unknown-service", + etag: "etag-000", + host: "codebuild", + state: defangv1.ServiceState_NOT_SPECIFIED, + }, + expectedState: defangv1.ServiceState_NOT_SPECIFIED, + }, + { + name: "Install Phase Event", + logEntry: &defangv1.LogEntry{ + Message: "Entering phase INSTALL", + Service: "install-service", + Etag: "etag-111", + Host: "host-install", + }, + expected: &CodebuildEvent{ + message: "Entering phase INSTALL", + service: "install-service", + etag: "etag-111", + host: "codebuild", + state: defangv1.ServiceState_BUILD_RUNNING, + }, + expectedState: defangv1.ServiceState_BUILD_RUNNING, + }, + { + name: "Pre-Build Phase Event", + logEntry: &defangv1.LogEntry{ + Message: "Entering phase PRE_BUILD", + Service: "prebuild-service", + Etag: "etag-222", + Host: "host-prebuild", + }, + expected: &CodebuildEvent{ + message: "Entering phase PRE_BUILD", + service: "prebuild-service", + etag: "etag-222", + host: "codebuild", + state: defangv1.ServiceState_BUILD_RUNNING, + }, + expectedState: defangv1.ServiceState_BUILD_RUNNING, + }, + { + name: "Build Phase Event", + logEntry: &defangv1.LogEntry{ + Message: "Entering phase BUILD", + Service: "build-service", + Etag: "etag-333", + Host: "host-build", + }, + expected: &CodebuildEvent{ + message: "Entering phase BUILD", + service: "build-service", + etag: "etag-333", + host: "codebuild", + state: defangv1.ServiceState_BUILD_RUNNING, + }, + expectedState: defangv1.ServiceState_BUILD_RUNNING, + }, + { + name: "Post-Build Phase Event", + logEntry: &defangv1.LogEntry{ + Message: "Entering phase POST_BUILD", + Service: "postbuild-service", + Etag: "etag-444", + Host: "host-postbuild", + }, + expected: &CodebuildEvent{ + message: "Entering phase POST_BUILD", + service: "postbuild-service", + etag: "etag-444", + host: "codebuild", + state: defangv1.ServiceState_BUILD_STOPPING, + }, + expectedState: defangv1.ServiceState_BUILD_STOPPING, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + event := ParseCodebuildEvent(tt.logEntry) + if event.Service() != tt.expected.Service() { + t.Errorf("expected service %s, got %s", tt.expected.Service(), event.Service()) + } + if event.Etag() != tt.expected.Etag() { + t.Errorf("expected etag %s, got %s", tt.expected.Etag(), event.Etag()) + } + if event.Host() != tt.expected.Host() { + t.Errorf("expected host %s, got %s", tt.expected.Host(), event.Host()) + } + if event.State() != tt.expected.State() { + t.Errorf("expected state %v, got %v", tt.expected.State(), event.State()) + } + }) + } +} From 6f0e03c2bc5f46be9a490badf81436da8a546b37 Mon Sep 17 00:00:00 2001 From: jordanstephens Date: Thu, 5 Feb 2026 15:30:28 -0800 Subject: [PATCH 4/6] avoid emiting NOT_SPECIFIED events --- src/pkg/cli/client/byoc/aws/stream.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pkg/cli/client/byoc/aws/stream.go b/src/pkg/cli/client/byoc/aws/stream.go index 879e4a350..91e2f5d1d 100644 --- a/src/pkg/cli/client/byoc/aws/stream.go +++ b/src/pkg/cli/client/byoc/aws/stream.go @@ -185,7 +185,7 @@ func (bs *byocServerStream) parseEvents(events []cw.LogEvent) *defangv1.TailResp entry.Etag = response.Etag entry.Host = response.Host evt := codebuild.ParseCodebuildEvent(entry) - if bs.codebuildEventHandler != nil { + if bs.codebuildEventHandler != nil && evt.State() != defangv1.ServiceState_NOT_SPECIFIED { bs.codebuildEventHandler.HandleCodebuildEvent(evt) } } else if (response.Service == "cd") && (strings.HasPrefix(entry.Message, logs.ErrorPrefix) || strings.Contains(strings.ToLower(entry.Message), "error:")) { From 6b2b7ed71cfa2ea2bbe47e6a06d6735ca813cdef Mon Sep 17 00:00:00 2001 From: jordanstephens Date: Fri, 6 Feb 2026 13:25:45 -0800 Subject: [PATCH 5/6] match codebuild event filtering with ecs event filtering --- src/pkg/cli/client/byoc/aws/subscribe.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/pkg/cli/client/byoc/aws/subscribe.go b/src/pkg/cli/client/byoc/aws/subscribe.go index c89b2266a..de87972c3 100644 --- a/src/pkg/cli/client/byoc/aws/subscribe.go +++ b/src/pkg/cli/client/byoc/aws/subscribe.go @@ -20,6 +20,12 @@ type byocSubscribeServerStream struct { } func (s *byocSubscribeServerStream) HandleCodebuildEvent(evt codebuild.Event) { + if etag := evt.Etag(); etag == "" || etag != s.etag { + return + } + if service := evt.Service(); len(s.services) > 0 && !slices.Contains(s.services, service) { + return + } resp := defangv1.SubscribeResponse{ Name: evt.Service(), Status: evt.Status(), From 96a856a99f06cdeb0d64fb22747192ef2825537e Mon Sep 17 00:00:00 2001 From: jordanstephens Date: Fri, 6 Feb 2026 14:51:38 -0800 Subject: [PATCH 6/6] cleanup --- src/pkg/cli/client/byoc/aws/stream.go | 2 +- src/pkg/cli/client/byoc/aws/subscribe.go | 4 ++- src/pkg/clouds/aws/codebuild/event.go | 33 ++++++++---------------- 3 files changed, 15 insertions(+), 24 deletions(-) diff --git a/src/pkg/cli/client/byoc/aws/stream.go b/src/pkg/cli/client/byoc/aws/stream.go index 91e2f5d1d..69ac5cbb8 100644 --- a/src/pkg/cli/client/byoc/aws/stream.go +++ b/src/pkg/cli/client/byoc/aws/stream.go @@ -181,7 +181,7 @@ func (bs *byocServerStream) parseEvents(events []cw.LogEvent) *defangv1.TailResp entry.Message = evt.Status() } } else if parseCodeBuildRecords { - entry.Service = strings.TrimSuffix(response.Service, "-image") + entry.Service = response.Service entry.Etag = response.Etag entry.Host = response.Host evt := codebuild.ParseCodebuildEvent(entry) diff --git a/src/pkg/cli/client/byoc/aws/subscribe.go b/src/pkg/cli/client/byoc/aws/subscribe.go index de87972c3..a48055726 100644 --- a/src/pkg/cli/client/byoc/aws/subscribe.go +++ b/src/pkg/cli/client/byoc/aws/subscribe.go @@ -2,6 +2,7 @@ package aws import ( "slices" + "strings" "github.com/DefangLabs/defang/src/pkg/clouds/aws/codebuild" "github.com/DefangLabs/defang/src/pkg/clouds/aws/ecs" @@ -23,7 +24,8 @@ func (s *byocSubscribeServerStream) HandleCodebuildEvent(evt codebuild.Event) { if etag := evt.Etag(); etag == "" || etag != s.etag { return } - if service := evt.Service(); len(s.services) > 0 && !slices.Contains(s.services, service) { + service := strings.TrimSuffix(evt.Service(), "-image") + if len(s.services) > 0 && !slices.Contains(s.services, service) { return } resp := defangv1.SubscribeResponse{ diff --git a/src/pkg/clouds/aws/codebuild/event.go b/src/pkg/clouds/aws/codebuild/event.go index 0ec09e269..75e080764 100644 --- a/src/pkg/clouds/aws/codebuild/event.go +++ b/src/pkg/clouds/aws/codebuild/event.go @@ -69,35 +69,24 @@ func (e *CodebuildEvent) Status() string { } func parseCodebuildMessage(message string) defangv1.ServiceState { - if strings.Contains(message, "Phase complete: ") && strings.Contains(message, "State: FAILED") { + switch { + case strings.Contains(message, "Phase complete: ") && strings.Contains(message, "State: FAILED"): return defangv1.ServiceState_BUILD_FAILED - } - if strings.Contains(message, "Running on CodeBuild") { + case strings.Contains(message, "Running on CodeBuild"): return defangv1.ServiceState_BUILD_ACTIVATING - } - if strings.Contains(message, "Phase is DOWNLOAD_SOURCE") { + case strings.Contains(message, "Phase is DOWNLOAD_SOURCE"): return defangv1.ServiceState_BUILD_RUNNING - } - - if strings.Contains(message, "Entering phase INSTALL") { + case strings.Contains(message, "Entering phase INSTALL"): return defangv1.ServiceState_BUILD_RUNNING - } - - if strings.Contains(message, "Entering phase PRE_BUILD") { + case strings.Contains(message, "Entering phase PRE_BUILD"): return defangv1.ServiceState_BUILD_RUNNING - } - - if strings.Contains(message, "Entering phase BUILD") { + case strings.Contains(message, "Entering phase BUILD"): return defangv1.ServiceState_BUILD_RUNNING - } - - if strings.Contains(message, "Entering phase POST_BUILD") { + case strings.Contains(message, "Entering phase POST_BUILD"): return defangv1.ServiceState_BUILD_STOPPING - } - - if strings.Contains(message, "Phase complete: UPLOAD_ARTIFACTS State: SUCCEEDED") { + case strings.Contains(message, "Phase complete: UPLOAD_ARTIFACTS State: SUCCEEDED"): return defangv1.ServiceState_DEPLOYMENT_PENDING + default: + return defangv1.ServiceState_NOT_SPECIFIED } - - return defangv1.ServiceState_NOT_SPECIFIED }