Skip to content

Commit d2d4121

Browse files
committed
tail build logs into build object
1 parent 245158d commit d2d4121

18 files changed

+410
-268
lines changed

api/protobuf-spec/github_com_openshift_origin_pkg_build_apis_build_v1.proto

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/swagger-spec/oapi-v1.json

+4
Original file line numberDiff line numberDiff line change
@@ -24586,6 +24586,10 @@
2458624586
"$ref": "v1.StageInfo"
2458724587
},
2458824588
"description": "stages contains details about each stage that occurs during the build including start time, duration (in milliseconds), and the steps that occured within each stage."
24589+
},
24590+
"logSnippet": {
24591+
"type": "string",
24592+
"description": "logSnippet is the last few lines of the build log. This value is only set for builds that failed."
2458924593
}
2459024594
}
2459124595
},

api/swagger-spec/openshift-openapi-spec.json

+4
Original file line numberDiff line numberDiff line change
@@ -82826,6 +82826,10 @@
8282682826
"type": "integer",
8282782827
"format": "int64"
8282882828
},
82829+
"logSnippet": {
82830+
"description": "logSnippet is the last few lines of the build log. This value is only set for builds that failed.",
82831+
"type": "string"
82832+
},
8282982833
"message": {
8283082834
"description": "message is a human-readable message indicating details about why the build has this status.",
8283182835
"type": "string"

pkg/build/apis/build/types.go

+3
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,9 @@ type BuildStatus struct {
304304
// including start time, duration (in milliseconds), and the steps that
305305
// occured within each stage.
306306
Stages []StageInfo
307+
308+
// LogSnippet is the last few lines of the build log. This value is only set for builds that failed.
309+
LogSnippet string
307310
}
308311

309312
// StageInfo contains details about a build stage.

pkg/build/apis/build/v1/generated.pb.go

+278-241
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/build/apis/build/v1/generated.proto

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/build/apis/build/v1/swagger_doc.go

+1
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ var map_BuildStatus = map[string]string{
204204
"config": "config is an ObjectReference to the BuildConfig this Build is based on.",
205205
"output": "output describes the Docker image the build has produced.",
206206
"stages": "stages contains details about each stage that occurs during the build including start time, duration (in milliseconds), and the steps that occured within each stage.",
207+
"logSnippet": "logSnippet is the last few lines of the build log. This value is only set for builds that failed.",
207208
}
208209

209210
func (BuildStatus) SwaggerDoc() map[string]string {

pkg/build/apis/build/v1/types.go

+3
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,9 @@ type BuildStatus struct {
213213
// including start time, duration (in milliseconds), and the steps that
214214
// occured within each stage.
215215
Stages []StageInfo `json:"stages,omitempty" protobuf:"bytes,11,opt,name=stages"`
216+
217+
// logSnippet is the last few lines of the build log. This value is only set for builds that failed.
218+
LogSnippet string `json:"logSnippet,omitempty" protobuf:"bytes,12,opt,name=logSnippet"`
216219
}
217220

218221
// StageInfo contains details about a build stage.

pkg/build/apis/build/v1/zz_generated.conversion.go

+2
Original file line numberDiff line numberDiff line change
@@ -811,6 +811,7 @@ func autoConvert_v1_BuildStatus_To_build_BuildStatus(in *BuildStatus, out *build
811811
return err
812812
}
813813
out.Stages = *(*[]build.StageInfo)(unsafe.Pointer(&in.Stages))
814+
out.LogSnippet = in.LogSnippet
814815
return nil
815816
}
816817

@@ -840,6 +841,7 @@ func autoConvert_build_BuildStatus_To_v1_BuildStatus(in *build.BuildStatus, out
840841
return err
841842
}
842843
out.Stages = *(*[]StageInfo)(unsafe.Pointer(&in.Stages))
844+
out.LogSnippet = in.LogSnippet
843845
return nil
844846
}
845847

pkg/build/controller/build/build_controller.go

+47-20
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package build
22

33
import (
44
"fmt"
5+
"strings"
56
"time"
67

78
"github.com/golang/glog"
@@ -42,6 +43,9 @@ import (
4243

4344
const (
4445
maxRetries = 15
46+
47+
// maxExcerptLength is the maximum length of the LogSnippet on a build.
48+
maxExcerptLength = 5
4549
)
4650

4751
// BuildController watches builds and synchronizes them with their
@@ -258,15 +262,19 @@ func shouldIgnore(build *buildapi.Build) bool {
258262
}
259263

260264
// If a build is in a terminal state, ignore it; unless it is in a succeeded or failed
261-
// state and its completion time is not set, then we should at least attempt to set its
262-
// completion time if possible.
265+
// state and its completion time or logsnippet is not set, then we should at least attempt to set its
266+
// completion time and logsnippet if possible because the build pod may have put the build in
267+
// this state and it would have not set the completion timestamp or logsnippet data.
263268
if buildutil.IsBuildComplete(build) {
264269
switch build.Status.Phase {
265-
case buildapi.BuildPhaseComplete,
266-
buildapi.BuildPhaseFailed:
270+
case buildapi.BuildPhaseComplete:
267271
if build.Status.CompletionTimestamp == nil {
268272
return false
269273
}
274+
case buildapi.BuildPhaseFailed:
275+
if build.Status.CompletionTimestamp == nil || len(build.Status.LogSnippet) == 0 {
276+
return false
277+
}
270278
}
271279
glog.V(4).Infof("Ignoring build %s in completed state", buildDesc(build))
272280
return true
@@ -546,17 +554,13 @@ func (bc *BuildController) handleActiveBuild(build *buildapi.Build, pod *v1.Pod)
546554
// handleCompletedBuild will only be called on builds that are already in a terminal phase however, their completion timestamp
547555
// has not been set.
548556
func (bc *BuildController) handleCompletedBuild(build *buildapi.Build, pod *v1.Pod) (*buildUpdate, error) {
549-
// Make sure that the completion timestamp has not already been set
550-
if build.Status.CompletionTimestamp != nil {
557+
// No-op if the completion timestamp and logsnippet data(for failed builds only) has already been set
558+
if build.Status.CompletionTimestamp != nil && (len(build.Status.LogSnippet) > 0 || build.Status.Phase != buildapi.BuildPhaseFailed) {
551559
return nil, nil
552560
}
553561

554562
update := &buildUpdate{}
555-
var podStartTime *metav1.Time
556-
if pod != nil {
557-
podStartTime = pod.Status.StartTime
558-
}
559-
setBuildCompletionTimestampAndDuration(build, podStartTime, update)
563+
setBuildCompletionData(build, pod, update)
560564

561565
return update, nil
562566
}
@@ -595,11 +599,7 @@ func (bc *BuildController) updateBuild(build *buildapi.Build, update *buildUpdat
595599

596600
// Update build completion timestamp if transitioning to a terminal phase
597601
if buildutil.IsTerminalPhase(*update.phase) {
598-
var podStartTime *metav1.Time
599-
if pod != nil {
600-
podStartTime = pod.Status.StartTime
601-
}
602-
setBuildCompletionTimestampAndDuration(build, podStartTime, update)
602+
setBuildCompletionData(build, pod, update)
603603
}
604604
glog.V(4).Infof("Updating build %s -> %s%s", buildDesc(build), *update.phase, reasonText)
605605
}
@@ -637,13 +637,13 @@ func (bc *BuildController) updateBuild(build *buildapi.Build, update *buildUpdat
637637
// patchBuild generates a patch for the given build and buildUpdate
638638
// and applies that patch using the REST client
639639
func (bc *BuildController) patchBuild(build *buildapi.Build, update *buildUpdate) (*buildapi.Build, error) {
640-
641640
// Create a patch using the buildUpdate object
642641
updatedBuild, err := buildutil.BuildDeepCopy(build)
643642
if err != nil {
644643
return nil, fmt.Errorf("cannot create a deep copy of build %s: %v", buildDesc(build), err)
645644
}
646645
update.apply(updatedBuild)
646+
647647
patch, err := validation.CreateBuildPatch(build, updatedBuild)
648648
if err != nil {
649649
return nil, fmt.Errorf("failed to create a build patch: %v", err)
@@ -818,12 +818,18 @@ func isValidTransition(from, to buildapi.BuildPhase) bool {
818818
return true
819819
}
820820

821-
// setBuildCompletionTimestampAndDuration sets the build completion time and duration as well as the start time
822-
// if not already set on the given buildUpdate object
823-
func setBuildCompletionTimestampAndDuration(build *buildapi.Build, podStartTime *metav1.Time, update *buildUpdate) {
821+
// setBuildCompletionData sets the build completion time and duration as well as the start time
822+
// if not already set on the given buildUpdate object. It also sets the log tail data
823+
// if applicable.
824+
func setBuildCompletionData(build *buildapi.Build, pod *v1.Pod, update *buildUpdate) {
824825
now := metav1.Now()
825826
update.setCompletionTime(now)
826827

828+
var podStartTime *metav1.Time
829+
if pod != nil {
830+
podStartTime = pod.Status.StartTime
831+
}
832+
827833
startTime := build.Status.StartTimestamp
828834
if startTime == nil {
829835
if podStartTime != nil {
@@ -834,4 +840,25 @@ func setBuildCompletionTimestampAndDuration(build *buildapi.Build, podStartTime
834840
update.setStartTime(*startTime)
835841
}
836842
update.setDuration(now.Rfc3339Copy().Time.Sub(startTime.Rfc3339Copy().Time))
843+
844+
if pod != nil && len(pod.Status.ContainerStatuses) != 0 && pod.Status.ContainerStatuses[0].State.Terminated != nil {
845+
msg := pod.Status.ContainerStatuses[0].State.Terminated.Message
846+
if len(msg) != 0 {
847+
parts := strings.Split(strings.TrimRight(msg, "\n"), "\n")
848+
849+
excerptLength := maxExcerptLength
850+
if len(parts) < maxExcerptLength {
851+
excerptLength = len(parts)
852+
}
853+
excerpt := parts[len(parts)-excerptLength:]
854+
for i, line := range excerpt {
855+
if len(line) > 120 {
856+
excerpt[i] = line[:58] + "..." + line[len(line)-59:]
857+
}
858+
}
859+
msg = strings.Join(excerpt, "\n")
860+
update.setLogSnippet(msg)
861+
}
862+
}
863+
837864
}

pkg/build/controller/build/build_controller_test.go

+17-5
Original file line numberDiff line numberDiff line change
@@ -267,10 +267,15 @@ func TestHandleBuild(t *testing.T) {
267267
update,
268268
},
269269
{
270-
name: "failed -> failed with completion timestamp",
271-
build: withCompletionTS(build(buildapi.BuildPhaseFailed)),
272-
pod: pod(v1.PodFailed),
273-
expectUpdate: nil,
270+
name: "failed -> failed with completion timestamp+message",
271+
build: withCompletionTS(build(buildapi.BuildPhaseFailed)),
272+
pod: pod(v1.PodFailed),
273+
expectOnComplete: true,
274+
expectUpdate: newUpdate().
275+
startTime(now).
276+
completionTime(now).
277+
logSnippet("").
278+
update,
274279
},
275280
}
276281

@@ -773,7 +778,9 @@ func TestSetBuildCompletionTimestampAndDuration(t *testing.T) {
773778

774779
for _, test := range tests {
775780
update := &buildUpdate{}
776-
setBuildCompletionTimestampAndDuration(test.build, test.podStartTime, update)
781+
pod := &v1.Pod{}
782+
pod.Status.StartTime = test.podStartTime
783+
setBuildCompletionData(test.build, pod, update)
777784
// Ensure that only the fields in the expected update are set
778785
if test.expected.podNameAnnotation == nil && (test.expected.podNameAnnotation != update.podNameAnnotation) {
779786
t.Errorf("%s: podNameAnnotation should not be set", test.name)
@@ -1186,6 +1193,11 @@ func (b *updateBuilder) podNameAnnotation(podName string) *updateBuilder {
11861193
return b
11871194
}
11881195

1196+
func (b *updateBuilder) logSnippet(message string) *updateBuilder {
1197+
b.update.setLogSnippet(message)
1198+
return b
1199+
}
1200+
11891201
type fakeRunPolicy struct {
11901202
notRunnable bool
11911203
onCompleteCalled bool

pkg/build/controller/build/buildupdate.go

+14-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type buildUpdate struct {
2626
completionTime *metav1.Time
2727
duration *time.Duration
2828
outputRef *string
29+
logSnippet *string
2930
}
3031

3132
func (u *buildUpdate) setPhase(phase buildapi.BuildPhase) {
@@ -60,6 +61,10 @@ func (u *buildUpdate) setPodNameAnnotation(podName string) {
6061
u.podNameAnnotation = &podName
6162
}
6263

64+
func (u *buildUpdate) setLogSnippet(message string) {
65+
u.logSnippet = &message
66+
}
67+
6368
func (u *buildUpdate) reset() {
6469
u.podNameAnnotation = nil
6570
u.phase = nil
@@ -69,6 +74,7 @@ func (u *buildUpdate) reset() {
6974
u.completionTime = nil
7075
u.duration = nil
7176
u.outputRef = nil
77+
u.logSnippet = nil
7278
}
7379

7480
func (u *buildUpdate) isEmpty() bool {
@@ -79,7 +85,8 @@ func (u *buildUpdate) isEmpty() bool {
7985
u.startTime == nil &&
8086
u.completionTime == nil &&
8187
u.duration == nil &&
82-
u.outputRef == nil
88+
u.outputRef == nil &&
89+
u.logSnippet == nil
8390
}
8491

8592
func (u *buildUpdate) apply(build *buildapi.Build) {
@@ -107,6 +114,9 @@ func (u *buildUpdate) apply(build *buildapi.Build) {
107114
if u.outputRef != nil {
108115
build.Status.OutputDockerImageReference = *u.outputRef
109116
}
117+
if u.logSnippet != nil {
118+
build.Status.LogSnippet = *u.logSnippet
119+
}
110120
}
111121

112122
// String returns a string representation of this update
@@ -137,5 +147,8 @@ func (u *buildUpdate) String() string {
137147
if u.podNameAnnotation != nil {
138148
updates = append(updates, fmt.Sprintf("podName: %q", *u.podNameAnnotation))
139149
}
150+
if u.logSnippet != nil {
151+
updates = append(updates, fmt.Sprintf("logSnippet: %q", *u.logSnippet))
152+
}
140153
return fmt.Sprintf("buildUpdate(%s)", strings.Join(updates, ", "))
141154
}

pkg/build/controller/strategy/custom.go

+1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ func (bs *CustomBuildStrategy) CreateBuildPod(build *buildapi.Build) (*v1.Pod, e
8989
SecurityContext: &v1.SecurityContext{
9090
Privileged: &privileged,
9191
},
92+
TerminationMessagePolicy: v1.TerminationMessageFallbackToLogsOnError,
9293
},
9394
},
9495
RestartPolicy: v1.RestartPolicyNever,

pkg/build/controller/strategy/docker.go

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ func (bs *DockerBuildStrategy) CreateBuildPod(build *buildapi.Build) (*v1.Pod, e
6060
SecurityContext: &v1.SecurityContext{
6161
Privileged: &privileged,
6262
},
63+
TerminationMessagePolicy: v1.TerminationMessageFallbackToLogsOnError,
6364
},
6465
},
6566
RestartPolicy: v1.RestartPolicyNever,

pkg/build/controller/strategy/sti.go

+1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ func (bs *SourceBuildStrategy) CreateBuildPod(build *buildapi.Build) (*v1.Pod, e
8484
Privileged: &privileged,
8585
},
8686
Args: []string{},
87+
TerminationMessagePolicy: v1.TerminationMessageFallbackToLogsOnError,
8788
},
8889
},
8990
RestartPolicy: v1.RestartPolicyNever,

pkg/cmd/cli/describe/describer.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,9 @@ func (d *BuildDescriber) Describe(namespace, name string, settings kprinters.Des
170170

171171
describeCommonSpec(build.Spec.CommonSpec, out)
172172
describeBuildTriggerCauses(build.Spec.TriggeredBy, out)
173-
173+
if len(build.Status.LogSnippet) != 0 {
174+
formatString(out, "Log Tail", build.Status.LogSnippet)
175+
}
174176
if settings.ShowEvents {
175177
kinternalprinters.DescribeEvents(events, kinternalprinters.NewPrefixWriter(out))
176178
}

pkg/openapi/zz_generated.openapi.go

+7
Original file line numberDiff line numberDiff line change
@@ -3084,6 +3084,13 @@ func GetOpenAPIDefinitions(ref openapi.ReferenceCallback) map[string]openapi.Ope
30843084
},
30853085
},
30863086
},
3087+
"logSnippet": {
3088+
SchemaProps: spec.SchemaProps{
3089+
Description: "logSnippet is the last few lines of the build log. This value is only set for builds that failed.",
3090+
Type: []string{"string"},
3091+
Format: "",
3092+
},
3093+
},
30873094
},
30883095
Required: []string{"phase"},
30893096
},

0 commit comments

Comments
 (0)