Skip to content

Commit 74bb541

Browse files
committed
tail build logs into build object
1 parent de7602b commit 74bb541

18 files changed

+396
-261
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

+3
Original file line numberDiff line numberDiff line change
@@ -24586,6 +24586,9 @@
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+
"logTail": {
24591+
"type": "string"
2458924592
}
2459024593
}
2459124594
},

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+
"logTail": {
82830+
"description": "LogTail 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+
// LogTail is the last few lines of the build log. This value is only set for builds that failed.
309+
LogTail *string
307310
}
308311

309312
// StageInfo contains details about a build stage.

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

+283-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+
"logTail": "LogTail 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+
// LogTail is the last few lines of the build log. This value is only set for builds that failed.
218+
LogTail *string `json:"logTail,omitempty" protobuf:"bytes,12,opt,name=logTail"`
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.LogTail = (*string)(unsafe.Pointer(in.LogTail))
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.LogTail = (*string)(unsafe.Pointer(in.LogTail))
843845
return nil
844846
}
845847

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

+5
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,11 @@ func DeepCopy_v1_BuildStatus(in interface{}, out interface{}, c *conversion.Clon
470470
}
471471
}
472472
}
473+
if in.LogTail != nil {
474+
in, out := &in.LogTail, &out.LogTail
475+
*out = new(string)
476+
**out = **in
477+
}
473478
return nil
474479
}
475480
}

pkg/build/apis/build/zz_generated.deepcopy.go

+5
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,11 @@ func DeepCopy_build_BuildStatus(in interface{}, out interface{}, c *conversion.C
471471
}
472472
}
473473
}
474+
if in.LogTail != nil {
475+
in, out := &in.LogTail, &out.LogTail
476+
*out = new(string)
477+
**out = **in
478+
}
474479
return nil
475480
}
476481
}

pkg/build/controller/build/build_controller.go

+38-19
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"
@@ -258,15 +259,19 @@ func shouldIgnore(build *buildapi.Build) bool {
258259
}
259260

260261
// 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.
262+
// state and its completion time or logtail is not set, then we should at least attempt to set its
263+
// completion time and logtail if possible because the build pod may have put the build in
264+
// this state and it would have not set the completion timestamp or logtail data.
263265
if buildutil.IsBuildComplete(build) {
264266
switch build.Status.Phase {
265-
case buildapi.BuildPhaseComplete,
266-
buildapi.BuildPhaseFailed:
267+
case buildapi.BuildPhaseComplete:
267268
if build.Status.CompletionTimestamp == nil {
268269
return false
269270
}
271+
case buildapi.BuildPhaseFailed:
272+
if build.Status.CompletionTimestamp == nil || build.Status.LogTail == nil {
273+
return false
274+
}
270275
}
271276
glog.V(4).Infof("Ignoring build %s in completed state", buildDesc(build))
272277
return true
@@ -546,17 +551,13 @@ func (bc *BuildController) handleActiveBuild(build *buildapi.Build, pod *v1.Pod)
546551
// handleCompletedBuild will only be called on builds that are already in a terminal phase however, their completion timestamp
547552
// has not been set.
548553
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 {
554+
// No-op if the completion timestamp and logtail data(for failed builds only) has already been set
555+
if build.Status.CompletionTimestamp != nil && (build.Status.LogTail != nil || build.Status.Phase != buildapi.BuildPhaseFailed) {
551556
return nil, nil
552557
}
553558

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

561562
return update, nil
562563
}
@@ -595,11 +596,7 @@ func (bc *BuildController) updateBuild(build *buildapi.Build, update *buildUpdat
595596

596597
// Update build completion timestamp if transitioning to a terminal phase
597598
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)
599+
setBuildCompletionData(build, pod, update)
603600
}
604601
glog.V(4).Infof("Updating build %s -> %s%s", buildDesc(build), *update.phase, reasonText)
605602
}
@@ -818,12 +815,18 @@ func isValidTransition(from, to buildapi.BuildPhase) bool {
818815
return true
819816
}
820817

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) {
818+
// setBuildCompletionData sets the build completion time and duration as well as the start time
819+
// if not already set on the given buildUpdate object. It also sets the log tail data
820+
// if applicable.
821+
func setBuildCompletionData(build *buildapi.Build, pod *v1.Pod, update *buildUpdate) {
824822
now := metav1.Now()
825823
update.setCompletionTime(now)
826824

825+
var podStartTime *metav1.Time
826+
if pod != nil {
827+
podStartTime = pod.Status.StartTime
828+
}
829+
827830
startTime := build.Status.StartTimestamp
828831
if startTime == nil {
829832
if podStartTime != nil {
@@ -834,4 +837,20 @@ func setBuildCompletionTimestampAndDuration(build *buildapi.Build, podStartTime
834837
update.setStartTime(*startTime)
835838
}
836839
update.setDuration(now.Rfc3339Copy().Time.Sub(startTime.Rfc3339Copy().Time))
840+
841+
if pod != nil && len(pod.Status.ContainerStatuses) != 0 && pod.Status.ContainerStatuses[0].State.Terminated != nil {
842+
msg := pod.Status.ContainerStatuses[0].State.Terminated.Message
843+
844+
parts := strings.Split(msg, "\n")
845+
// 6 will get us 5 lines because the last entry in parts is an empty string.
846+
excerptLength := 6
847+
if len(parts) < excerptLength {
848+
excerptLength = len(parts)
849+
}
850+
851+
msg = strings.Join(parts[len(parts)-excerptLength:], "\n")
852+
853+
update.setLogTail(msg)
854+
}
855+
837856
}

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+
logTail *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) setLogTail(message string) {
65+
u.logTail = &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.logTail = 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.logTail == 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.logTail != nil {
118+
build.Status.LogTail = u.logTail
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.logTail != nil {
151+
updates = append(updates, fmt.Sprintf("logTail: %q", *u.logTail))
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/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+
"logTail": {
3088+
SchemaProps: spec.SchemaProps{
3089+
Description: "LogTail 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
},

test/extended/builds/failure_status.go

+19
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ package builds
22

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

67
g "github.com/onsi/ginkgo"
78
o "github.com/onsi/gomega"
89

910
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
"k8s.io/apimachinery/pkg/util/wait"
1012

1113
s2istatus "github.com/openshift/source-to-image/pkg/util/status"
1214

@@ -57,6 +59,23 @@ var _ = g.Describe("[builds][Slow] update failure status", func() {
5759
o.Expect(build.Status.Message).To(o.Equal(buildapi.StatusMessagePostCommitHookFailed))
5860

5961
exutil.CheckForBuildEvent(oc.KubeClient().Core(), br.Build, buildapi.BuildFailedEventReason, buildapi.BuildFailedEventMessage)
62+
63+
// wait for the build to be updated w/ completiontimestamp which should also mean the logtail
64+
// is set if one is going to be set.
65+
err = wait.Poll(time.Second, 30*time.Second, func() (bool, error) {
66+
// note this is the same build variable used in the test scope
67+
build, err = oc.Client().Builds(oc.Namespace()).Get(br.Build.Name, metav1.GetOptions{})
68+
if err != nil {
69+
return true, err
70+
}
71+
if build.Status.CompletionTimestamp != nil {
72+
return true, nil
73+
}
74+
return false, nil
75+
})
76+
o.Expect(err).NotTo(o.HaveOccurred())
77+
o.Expect(build.Status.LogTail).NotTo(o.BeNil(), "LogTail should be set to something for failed builds")
78+
o.Expect(len(*build.Status.LogTail)).NotTo(o.Equal(0), "LogTail should be set to something for failed builds")
6079
})
6180
})
6281

0 commit comments

Comments
 (0)